# Copyright (C) 2024 Cetmix OÜ
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError

from .cx_tower_file import TEMPLATE_FILE_FIELD_MAPPING


class CxTowerFileTemplate(models.Model):
    """File template to manage multiple files at once"""

    _name = "cx.tower.file.template"
    _inherit = [
        "cx.tower.reference.mixin",
        "cx.tower.key.mixin",
        "cx.tower.template.mixin",
        "cx.tower.access.role.mixin",
        "cx.tower.tag.mixin",
    ]
    _description = "Cetmix Tower File Template"
    _order = "name"

    active = fields.Boolean(default=True)
    file_name = fields.Char(
        help="Default full file name with file type for example: test.txt",
    )
    code = fields.Text(string="File content")
    server_dir = fields.Char(string="Directory on server")
    file_ids = fields.One2many("cx.tower.file", "template_id")
    file_count = fields.Integer(
        "File(s)",
        compute="_compute_file_count",
    )
    tag_ids = fields.Many2many(
        relation="cx_tower_file_template_tag_rel",
        column1="file_template_id",
        column2="tag_id",
    )
    note = fields.Text(help="This field is used to put some notes regarding template.")
    keep_when_deleted = fields.Boolean(
        help="File will be kept on server when deleted in Tower",
    )
    auto_sync = fields.Boolean(
        help="If enabled, files created from this template will have "
        "Auto Sync enabled by default. Used only with 'Tower' source.",
    )
    file_type = fields.Selection(
        selection=lambda self: self.env["cx.tower.file"]._selection_file_type(),
        default=lambda self: self.env["cx.tower.file"]._default_file_type(),
        required=True,
    )
    source = fields.Selection(
        [
            ("tower", "Tower"),
            ("server", "Server"),
        ],
        required=True,
        default="tower",
    )
    variable_ids = fields.Many2many(
        comodel_name="cx.tower.variable",
        relation="cx_tower_file_template_variable_rel",
        column1="file_template_id",
        column2="variable_id",
    )

    # ---- Access. Add relation for mixin fields
    user_ids = fields.Many2many(
        relation="cx_tower_file_template_user_rel",
        domain=lambda self: [
            ("groups_id", "in", [self.env.ref("cetmix_tower_server.group_manager").id])
        ],
    )
    manager_ids = fields.Many2many(
        relation="cx_tower_file_template_manager_rel",
    )

    @classmethod
    def _get_depends_fields(cls):
        """
        Define dependent fields for computing
        `variable_ids` in file template-related models.

        This implementation specifies that the fields `code`, `server_dir`,
        and `file_name` are used to compute the
        variables associated with a file template.

        Returns:
            list: A list of field names (str) representing the dependencies.

        Example:
            The following fields trigger recomputation
            of `variable_ids`:
            - `code`: The template content for the file.
            - `server_dir`: The target directory on the
            server where the template is applied.
            - `file_name`: The name of the generated file.
        """
        return ["code", "server_dir", "file_name"]

    # -- Computes
    @api.depends("file_ids")
    def _compute_file_count(self):
        """
        Compute total template files
        """
        for template in self:
            template.file_count = len(template.file_ids)

    # -- Create/Write/Unlink
    def write(self, vals):
        """
        Override to update files related with the templates
        """
        result = super().write(vals)
        if any([field_ in vals for field_ in TEMPLATE_FILE_FIELD_MAPPING]):
            for file in self.mapped("file_ids"):
                file.write(file._get_file_values_from_related_template())
        return result

    # -- Actions
    def action_open_files(self):
        """
        Open current template files
        """
        action = self.env["ir.actions.actions"]._for_xml_id(
            "cetmix_tower_server.cx_tower_file_action"
        )
        action["domain"] = [("id", "in", self.file_ids.ids)]
        return action

    # -- Business logic
    def create_file(self, server, server_dir="", if_file_exists="raise"):
        """
        Create a new file using the current template for the selected server.
        If the same file already exists, just ignore it or raise an error based on the
        parameter.

        :param server: recordset
            The server (cx.tower.server) on which the file should be created. This is a
            required parameter.
        :param if_file_exists: str, optional
            Defines the behavior if the file already exists on the server.
        :param server_dir: str, optional
            The directory on the server where the file should be created. If not set,
            the server_dir field of the template will be used.

        :return: cx.tower.file
            Returns the newly created file record (cx.tower.file) if the file was
            created successfully or if_file_exists is set to "overwrite".
            Returns the existing file record if the file already exists
            and if_file_exists is set to "skip".

        :raises ValidationError:
            If the file already exists on the server if_file_exists is set to "raise".
        """
        self.ensure_one()
        # Explicit guard against invalid behavior values
        valid_behaviors = {"skip", "raise", "overwrite"}
        if if_file_exists not in valid_behaviors:
            raise ValidationError(
                f"Invalid if_file_exists value: {if_file_exists}. "
                f"Expected one of {valid_behaviors}."
            )
        file_model = self.env["cx.tower.file"]
        existing_files = file_model.search(
            [
                ("server_id", "=", server.id),
                ("source", "=", self.source),
            ],
            order="id DESC",
        )
        existing_dir = server_dir or self.server_dir

        # Render the server directory and file name from the template
        variables = list(
            set(
                self.get_variables_from_code(self.file_name)
                + self.get_variables_from_code(existing_dir)
            )
        )
        var_vals = server.get_variable_values(variables).get(server.id) or {}

        unrendered_path = (
            f"{existing_dir}/{self.file_name}" if existing_dir else self.file_name
        )
        rendered_path = self.render_code_custom(unrendered_path, **var_vals)

        # Filter existing files by rendered path
        existing_files = existing_files.filtered(
            lambda f: f.full_server_path == rendered_path
        )

        # Filter existing files by template if it exists, otherwise take the first one
        existing_file = (
            existing_files.filtered(lambda f: f.template_id == self)[:1]
            or existing_files[:1]
        )

        if existing_file and if_file_exists == "skip":
            return existing_file.with_context(file_creation_skipped=True)

        if existing_file and if_file_exists == "raise":
            raise ValidationError(_("File already exists on server."))

        if existing_file and if_file_exists == "overwrite":
            existing_file.with_context(is_custom_server_dir=True).write(
                {
                    "template_id": self.id,
                }
            )
            return existing_file

        vals = {
            "name": self.file_name,
            "server_id": server.id,
            "server_dir": existing_dir,
            "template_id": self.id,
            "code": self.code,
            "file_type": self.file_type,
            "source": self.source,
            "auto_sync": self.auto_sync,
        }

        new_file = file_model.with_context(is_custom_server_dir=True).create(vals)
        # Return new_file if no file exists
        return new_file
