# Copyright 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited
from odoo import _, api, fields, models
from odoo.exceptions import UserError

from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG

from ..identifier_adapter import IdentifierAdapter


class CleanupPurgeLineField(models.TransientModel):
    _inherit = "cleanup.purge.line"
    _name = "cleanup.purge.line.field"
    _description = "Purge fields"

    wizard_id = fields.Many2one(
        "cleanup.purge.wizard.field", "Purge Wizard", readonly=True
    )
    field_id = fields.Many2one(
        comodel_name="ir.model.fields",
        string="Field",
    )
    model_id = fields.Many2one(
        comodel_name="ir.model",
        related="field_id.model_id",
        string="Model",
        store=True,
    )
    model_name = fields.Char(
        related="model_id.model",
        string="Model Technical Name",
        store=True,
    )

    def purge(self):
        """
        Unlink fields upon manual confirmation.
        """
        context_flags = {
            MODULE_UNINSTALL_FLAG: True,
            "purge": True,
        }
        if self:
            objs = self
        else:
            objs = self.env["cleanup.purge.line.action"].browse(
                self._context.get("active_ids")
            )
        to_unlink = objs.filtered(lambda x: not x.purged and x.field_id)
        self.logger.info("Purging field entries:")
        for rec in to_unlink:
            self.logger.info(" - %s.%s", rec.model_name, rec.field_id.name)
            field_id = rec.with_context(**context_flags).field_id
            model = self.env[rec.model_name]
            table_name = model._table
            column_name = field_id.name
            force_drop = False
            # FIX: on unlink, odoo will not DROP the SQL column even if exists if the
            # store attribute is set to False.
            if not field_id.store and model._auto:
                force_drop = True
            # Odoo will internally drop the SQL column
            field_id.unlink()
            if force_drop:
                self._drop_column(table_name, column_name)
            rec.purged = True
        return True

    def _drop_column(self, table, column):
        # Use code from `purge_columns.py::purge()`
        # Check whether the column actually still exists.
        # Inheritance such as stock.picking.in from stock.picking
        # can lead to double attempts at removal
        self.env.cr.execute(
            "SELECT count(attname) FROM pg_attribute "
            "WHERE attrelid = "
            "( SELECT oid FROM pg_class WHERE relname = %s ) "
            "AND attname = %s",
            (table, column),
        )
        if not self.env.cr.fetchone()[0]:
            return
        self.logger.info("Dropping column %s from table %s", column, table)
        self.env.cr.execute(
            "ALTER TABLE %s DROP COLUMN %s",
            (IdentifierAdapter(table), IdentifierAdapter(column)),
        )
        # we need this commit because the ORM will deadlock if
        # we still have a pending transaction
        self.env.cr.commit()  # pylint: disable=invalid-commit


class CleanupPurgeWizardField(models.TransientModel):
    _inherit = "cleanup.purge.wizard"
    _name = "cleanup.purge.wizard.field"
    _description = "Purge fields"

    @api.model
    def find(self):
        """
        Search for fields not technically mapped to a model.
        """
        res = []
        ignored_fields = models.MAGIC_COLUMNS + [
            "display_name",
        ]
        domain = [("state", "=", "base")]
        for field_id in self.env["ir.model.fields"].search(domain):
            if field_id.name in ignored_fields:
                continue
            model = self.env[field_id.model_id.model]
            if field_id.name not in model._fields.keys():
                res.append(
                    (
                        0,
                        0,
                        {
                            "name": field_id.name,
                            "field_id": field_id.id,
                        },
                    )
                )
        if not res:
            raise UserError(_("No orphaned fields found"))
        return res

    purge_line_ids = fields.One2many(
        "cleanup.purge.line.field", "wizard_id", "Fields to purge"
    )
