"""
Silique (https://www.silique.fr)
Copyright (C) 2022-2025
            
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from typing import Any, List, Optional
from io import BytesIO

from rich.tree import Tree
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from ruamel.yaml import YAML

from tiramisu import undefined
from tiramisu.error import PropertiesOptionError, ConfigError

from .i18n import _


class RougailOutputConsole:
    variable_hidden_color = "orange1"
    variable_advanced_color = "bright_blue"
    variable_advanced_and_modified_color = "red1"
    value_unmodified_color = "gold1"
    value_default_color = "green"

    def __init__(
        self,
        config: "Config",
        rougailconfig: "RougailConfig" = None,
        user_data_errors: Optional[list] = None,
        user_data_warnings: Optional[list] = None,
    ) -> None:
        if rougailconfig is None:
            from rougail import RougailConfig

            rougailconfig = RougailConfig
        self.rougailconfig = rougailconfig
        self.config = config
        self.read_write = self.rougailconfig["console.read_write"]
        self.is_mandatory = self.rougailconfig["console.mandatory"]
        self.show_secrets = self.rougailconfig["console.show_secrets"]
        self.errors = []
        self.warnings = []
        if user_data_errors is None:
            user_data_errors = []
        self.user_data_errors = user_data_errors
        if user_data_warnings is None:
            user_data_warnings = []
        self.user_data_warnings = user_data_warnings
        self.console = Console(force_terminal=True)
        self.out = []
        self.root = self.get_root()

    def mandatory(self):
        if not self.is_mandatory:
            return
        title = False
        options_with_error = []
        try:
            mandatories = self.config.value.mandatory()
        except (ConfigError, PropertiesOptionError) as err:
            self.errors.append(_("Error in config: {0}").format(err))
            return
        except ValueError as err:
            self.errors.append(str(err))
            return
        for option in mandatories:
            try:
                option.value.get()
                if not title:
                    # self.errors.append("Les variables suivantes sont obligatoires mais n'ont pas de valeur :")
                    self.errors.append(
                        _("The following variables are mandatory but have no value:")
                    )
                    title = True
                self.errors.append(f"  - {option.description()}")
            except PropertiesOptionError:
                options_with_error.append(option)
        if not title:
            for idx, option in enumerate(options_with_error):
                if not idx:
                    # self.errors.append("Les variables suivantes sont inaccessibles mais sont vides et obligatoires :")
                    self.errors.append(
                        _(
                            "The following variables are inaccessible but are empty and mandatory :"
                        )
                    )
                self.errors.append(f"  - {option.description()}")

    def exporter(self) -> bool:
        self.config.property.read_write()
        self.mandatory()
        if self.read_write:
            self.config.property.read_write()
        else:
            self.config.property.read_only()
        errors = self.user_data_errors + self.errors
        if errors:
            self.display_errors(errors)
            return False
        warnings = self.user_data_warnings + self.warnings
        if warnings:
            self.display_warnings(warnings)
        self.header()
        self.parse_options(
            self.config,
            self.root,
        )
        self.end()
        return True

    def run(self) -> str:
        with self.console.capture() as capture:
            ret = self.print()
        return ret, capture.get()

    def print(self) -> None:
        ret = self.exporter()
        for out in self.out:
            self.console.print(out)
        return ret

    def parse_options(
        self,
        conf,
        parent,
    ):
        for option in conf:
            if option.isoptiondescription():
                family = parent.add_family(option)
                if option.isleadership():
                    self.parse_leadership(
                        option,
                        family,
                    )
                else:
                    self.parse_options(
                        option,
                        family,
                    )
            else:
                parent.add_variable(option)

    def parse_leadership(
        self,
        conf,
        parent,
    ):
        leader, *followers = list(conf)
        leader_values = leader.value.get()
        for idx, leader_value in enumerate(leader_values):
            leader_obj = parent.add_family(leader)
            leader_obj.add_variable(
                leader,
                value=leader_value,
                leader_index=idx,
            )
            for follower in followers:
                if follower.index() != idx:
                    continue
                leader_obj.add_variable(follower)

    def header(self):
        header_variable = "Variable\n"
        # header_variable += f'[{self.variable_advanced_color}]Variable non documentée[/{self.variable_advanced_color}]\n'
        # header_variable += f'[{self.variable_advanced_and_modified_color}]Variable non documentée mais modifiée[/{self.variable_advanced_and_modified_color}]'
        header_variable += f'[{self.variable_advanced_color}]{_("Undocumented variable")}[/{self.variable_advanced_color}]\n'
        header_variable += f'[{self.variable_advanced_and_modified_color}]{_("Undocumented but modified variable")}[/{self.variable_advanced_and_modified_color}]'
        if not self.read_write:
            # header_variable += f'\n[{self.variable_hidden_color}]Variable non modifiable[/{self.variable_hidden_color}]'
            header_variable += f'\n[{self.variable_hidden_color}]{_("Unmodifiable variable")}[/{self.variable_hidden_color}]'
        # header_value = f'[{self.value_unmodified_color}]Valeur par défaut[/{self.value_unmodified_color}]\n'
        # header_value += 'Valeur modifiée\n'
        # header_value += f'([{self.value_default_color}]Valeur par défaut originale[/{self.value_default_color}])'
        header_value = f'[{self.value_unmodified_color}]{_("Default value")}[/{self.value_unmodified_color}]\n'
        header_value += _("Modified value") + "\n"
        header_value += f'([{self.value_default_color}]{_("Original default value")}[/{self.value_default_color}])'
        header = Table.grid(padding=1, collapse_padding=True)
        header.pad_edge = False
        header.add_row(header_variable, header_value)
        self.out.append(Panel.fit(header, title=_("Caption")))

    def display_errors(
        self,
        errors,
    ) -> None:
        tree = Tree(
            f":stop_sign: {_('ERRORS')}",
            guide_style="bold bright_red",
        )
        for error in errors:
            tree.add(error)
        self.out.append(tree)

    def display_warnings(
        self,
        warnings: list,
    ) -> None:
        tree = Tree(":warning: WARNINGS")
        for warning in warnings:
            tree.add(warning)
        self.out.append(tree)

    def get_root(self) -> None:
        yaml = YAML()
        yaml.indent(mapping=2, sequence=4, offset=2)
        self.output = OutputFamily(
            _("Variables:"),
            None,
            self,
            yaml,
            no_icon=True,
        )
        return self.output

    def end(self):
        self.out.append(self.output.tree)


class OutputFamily:
    def __init__(
        self, family, parent, root, _yaml, *, is_leader: bool = False, no_icon: bool = False
    ) -> None:
        if parent is None:
            tree = Tree
        else:
            tree = parent.add
        if is_leader:
            self.tree = tree(
                f":notebook: {family} :",
                guide_style="bold bright_blue",
            )
        elif no_icon:
            self.tree = tree(
                f"{family}",
                guide_style="bold bright_blue",
            )
        else:
            self.tree = tree(
                f":open_file_folder: {family}",
                guide_style="bold bright_blue",
            )
        self.root = root
        self._yaml = _yaml

    def add_family(
        self,
        option,
    ) -> None:
        properties = option.property.get()
        if "hidden" in properties:
            color = self.root.variable_hidden_color
        elif "advanced" in properties:
            color = self.root.variable_advanced_color
        else:
            color = None
        return OutputFamily(
            self.colorize(
                None,
                option.name(),
                color,
                None,
            ),
            self.tree,
            self.root,
            self._yaml,
        )

    def add_variable(
        self, option, value: Any = undefined, leader_index: Optional[int] = None
    ):
        properties = option.property.get()
        variable_color = None
        if option.owner.isdefault():
            if "hidden" in properties:
                variable_color = self.root.variable_hidden_color
            elif "advanced" in properties:
                variable_color = self.root.variable_advanced_color
            color = self.root.value_unmodified_color
            default_value = None
        else:
            if "hidden" in properties:
                variable_color = self.root.variable_hidden_color
            elif "advanced" in properties:
                variable_color = self.root.variable_advanced_and_modified_color
            color = None
            if option.information.get("default_value_makes_sense", True):
                try:
                    default_value = option.value.default()
                except ConfigError:
                    if option.ismulti():
                        default_value = []
                    else:
                        default_value = None
                if leader_index is not None:
                    if len(default_value) > leader_index:
                        default_value = default_value[leader_index]
                    else:
                        default_value = None
            else:
                default_value = None
        if value is undefined:
            value = option.value.get()
        key = self.colorize(
            None,
            option.name(),
            variable_color,
            None,
        )
        value = self.colorize(
            option,
            value,
            color,
            default_value,
        )
        if isinstance(value, list):
            subtree = self.tree.add(
                f":notebook: {key} :",
                guide_style="bold bright_blue",
            )
            for val in value:
                subtree.add(str(val))
        else:
            self.tree.add(f":notebook: {key}: {value}")

    def colorize(
        self,
        option,
        value,
        color: str,
        default_value,
    ) -> str:
        if isinstance(value, list):
            if default_value is None:
                default_value = []
#            default_value = [self.convert_value(option, val) for val in default_value]
            len_value = len(value)
            len_default_value = len(default_value)
            len_values = max(len_value, len_default_value)
            ret = []
            for idx in range(len_values):
                if idx < len_value:
                    val = value[idx]
                else:
                    val = ""
                if idx < len_default_value:
                    default = default_value[idx]
                else:
                    default = None
                ret.append(
                    self.colorize(
                        option,
                        val,
                        color,
                        default,
                    )
                )
            return ret
        if option:
            value = self.convert_value(
                option,
                value,
            )
        if color is not None:
            ret = f"[{color}]{value}[/{color}]"
        else:
            ret = value
        if default_value is not None and "force_store_value" not in option.property.get():
            default_value_color = self.root.value_default_color
            default_value = self.convert_value(option, default_value)
            if ret:
                ret += f" ([{default_value_color}]{default_value}[/{default_value_color}])"
            else:
                ret = f"[{default_value_color}]{default_value}[/{default_value_color}]"
        return ret

    def convert_value(
        self,
        option,
        value: Any,
    ) -> str:
        if isinstance(value, list):
            print(value)
            raise Exception('pfff')
        """Dump variable, means transform bool, ... to yaml string"""
        if not self.root.show_secrets and option.type() == "password":
            return "*" * 10
        if isinstance(value, str):
            return value
        with BytesIO() as ymlfh:
            self._yaml.dump(value, ymlfh)
            ret = ymlfh.getvalue().decode("utf-8").strip()
        if ret.endswith("..."):
            ret = ret[:-3].strip()
        return ret


RougailOutput = RougailOutputConsole


__all__ = ("RougailOutputConsole",)
