# Copyright 2011-2015 Therp BV <https://therp.nl>
# Copyright 2016-2020 Opener B.V. <https://opener.am>
# Copyright 2019 ForgeFlow <https://forgeflow.com>
# Copyright 2020 GRAP <https://grap.coop>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import ast
import logging
import os

from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.modules.module import MANIFEST_NAMES, get_module_path
from odoo.tools.translate import _

_logger = logging.getLogger(__name__)


class UpgradeRecord(models.Model):
    _name = "upgrade.record"
    _description = "Upgrade Record"

    name = fields.Char(readonly=True)

    module = fields.Char(readonly=True)

    model = fields.Char(readonly=True)

    field = fields.Char(readonly=True)

    mode = fields.Selection(
        [("create", "Create"), ("modify", "Modify")],
        help="Set to Create if a field is newly created "
        "in this module. If this module modifies an attribute of an "
        "existing field, set to Modify.",
        readonly=True,
    )

    type = fields.Selection(
        [("field", "Field"), ("xmlid", "XML ID"), ("model", "Model")],
        readonly=True,
    )

    attribute_ids = fields.One2many(
        comodel_name="upgrade.attribute", inverse_name="record_id", readonly=True
    )

    noupdate = fields.Boolean(readonly=True)

    domain = fields.Char(readonly=True)

    definition = fields.Char(readonly=True)

    prefix = fields.Char(compute="_compute_prefix_and_suffix")

    suffix = fields.Char(compute="_compute_prefix_and_suffix")

    model_original_module = fields.Char(compute="_compute_model_original_module")

    model_type = fields.Char(compute="_compute_model_type")

    @api.depends("name")
    def _compute_prefix_and_suffix(self):
        for rec in self:
            rec.prefix, rec.suffix = rec.name.split(".", 1)

    @api.depends("model", "type")
    def _compute_model_original_module(self):
        for rec in self:
            if rec.type == "model":
                rec.model_original_module = self.env[rec.model]._original_module
            else:
                rec.model_original_module = ""

    @api.depends("model", "type")
    def _compute_model_type(self):
        for rec in self:
            if rec.type == "model":
                model = self.env[rec.model]
                if model._auto and model._transient:
                    rec.model_type = "transient"
                elif model._auto:
                    rec.model_type = ""
                elif not model._auto and model._abstract:
                    rec.model_type = "abstract"
                else:
                    rec.model_type = "sql_view"
            else:
                rec.model_type = ""

    @api.model
    def field_dump(self):
        keys = [
            "attachment",
            "module",
            "mode",
            "model",
            "field",
            "type",
            "isfunction",
            "isproperty",
            "isrelated",
            "relation",
            "required",
            "stored",
            "selection_keys",
            "hasdefault",
            "table",
            "_inherits",
            "_order",
        ]

        template = {x: False for x in keys}
        data = []
        for record in self.search([("type", "=", "field")]):
            repre = template.copy()
            repre.update(
                {
                    "module": record.module,
                    "model": record.model,
                    "field": record.field,
                    "mode": record.mode,
                }
            )
            repre.update({x.name: x.value for x in record.attribute_ids})
            if repre["table"]:
                repre.update(
                    {
                        "column1": self.env[repre["model"]]
                        ._fields[repre["field"]]
                        .column1,
                        "column2": self.env[repre["model"]]
                        ._fields[repre["field"]]
                        .column2,
                    }
                )
            data.append(repre)
        return data

    @api.model
    def list_modules(self):
        """Return the set of covered modules"""
        self.env.cr.execute(
            """SELECT DISTINCT(module) FROM upgrade_record
            ORDER BY module"""
        )
        return [module for module, in self.env.cr.fetchall()]

    @staticmethod
    def _read_manifest(addon_dir):
        for manifest_name in MANIFEST_NAMES:
            if os.access(os.path.join(addon_dir, manifest_name), os.R_OK):
                with open(os.path.join(addon_dir, manifest_name), "r") as f:
                    manifest_string = f.read()
                    return ast.literal_eval(manifest_string)
        raise ValidationError(
            _("No manifest found in %(addon_dir)s") % {"addon_dir": addon_dir}
        )

    @api.model
    def get_xml_records(self, module):
        """Return all XML records from the given module"""
        addon_dir = get_module_path(module)
        manifest = self._read_manifest(addon_dir)
        # The order of the keys are important.
        # Load files in the same order as in
        # module/loading.py:load_module_graph
        files = []
        for key in ["init_xml", "update_xml", "data"]:
            if not manifest.get(key):
                continue
            for xml_file in manifest[key]:
                if not xml_file.lower().endswith(".xml"):
                    continue
                parts = xml_file.split("/")
                try:
                    with open(os.path.join(addon_dir, *parts), "r") as xml_handle:
                        files.append(xml_handle.read())
                except UnicodeDecodeError:
                    _logger.warning(
                        "Encoding error: Unable to read %s",
                        os.path.join(addon_dir, *parts),
                    )
                    continue
        return files
