# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

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

from .diff_utils import apply_patch, generate_comparison, generate_patch


class HtmlFieldHistory(models.AbstractModel):
    _name = "html.field.history.mixin"
    _description = "Field html History"
    _html_field_history_size_limit = 300

    html_field_history = fields.Json("History data", prefetch=False)

    html_field_history_metadata = fields.Json(
        "History metadata", compute="_compute_metadata"
    )

    @api.model
    def _get_versioned_fields(self):
        """This method should be overriden

        :return: List[string]: A list of name of the fields to be versioned
        """
        return []

    @api.depends("html_field_history")
    def _compute_metadata(self):
        for rec in self:
            history_metadata = None
            if rec.html_field_history:
                history_metadata = {}
                for field_name in rec.html_field_history:
                    history_metadata[field_name] = []
                    for revision in rec.html_field_history[field_name]:
                        metadata = revision.copy()
                        metadata.pop("patch")
                        history_metadata[field_name].append(metadata)
            rec.html_field_history_metadata = history_metadata

    def write(self, vals):
        new_revisions = False
        db_contents = None
        versioned_fields = self._get_versioned_fields()
        vals_contain_versioned_fields = set(vals).intersection(versioned_fields)

        if vals_contain_versioned_fields:
            self.ensure_one()
            db_contents = dict([(f, self[f]) for f in versioned_fields])
            fields_data = self.env[self._name]._fields

            if any(f in vals and not fields_data[f].sanitize for f in versioned_fields):
                raise ValidationError(
                    "Ensure all versioned fields ( %s ) in model %s are declared as sanitize=True"
                    % (str(versioned_fields), self._name)
                )

        # Call super().write before generating the patch to be sure we perform
        # the diff on sanitized data
        write_result = super().write(vals)

        if not vals_contain_versioned_fields:
            return write_result

        history_revs = self.html_field_history or {}

        for field in versioned_fields:
            new_content = self[field] or ""

            if field not in history_revs:
                history_revs[field] = []

            old_content = db_contents[field] or ""
            if new_content != old_content:
                new_revisions = True
                patch = generate_patch(new_content, old_content)
                revision_id = (
                    (history_revs[field][0]["revision_id"] + 1)
                    if history_revs[field]
                    else 1
                )

                history_revs[field].insert(
                    0,
                    {
                        "patch": patch,
                        "revision_id": revision_id,
                        "create_date": self.env.cr.now().isoformat(),
                        "create_uid": self.env.uid,
                        "create_user_name": self.env.user.name,
                    },
                )
                limit = self._html_field_history_size_limit
                history_revs[field] = history_revs[field][:limit]
        # Call super().write again to include the new revision
        if new_revisions:
            extra_vals = {"html_field_history": history_revs}
            write_result = super().write(extra_vals) and write_result
        return write_result

    def html_field_history_get_content_at_revision(self, field_name, revision_id):
        """Get the requested field content restored at the revision_id.

        :param str field_name: the name of the field
        :param int revision_id: id of the last revision to restore

        :return: string: the restored content
        """
        self.ensure_one()

        revisions = [
            i
            for i in self.html_field_history[field_name]
            if i["revision_id"] >= revision_id
        ]

        content = self[field_name] or ""
        for revision in revisions:
            content = apply_patch(content, revision["patch"])

        return content

    def html_field_history_get_comparison_at_revision(self, field_name, revision_id):
        """For the requested field,
        Get a comparison between the current content of the field and the
        content restored at the requested revision_id.

        :param str field_name: the name of the field
        :param int revision_id: id of the last revision to compare

        :return: string: the comparison
        """
        self.ensure_one()
        restored_content = self.html_field_history_get_content_at_revision(
            field_name, revision_id
        )

        return generate_comparison(self[field_name] or "", restored_content)
