from odoo import api, fields, models, _
from odoo.fields import Domain
from odoo.tools import SQL, Query
from odoo.exceptions import UserError


class AccountAccountTag(models.Model):
    _name = 'account.account.tag'
    _description = 'Account Tag'

    name = fields.Char('Tag Name', required=True, translate=True)
    applicability = fields.Selection([('accounts', 'Accounts'), ('taxes', 'Taxes'), ('products', 'Products')], required=True, default='accounts')
    color = fields.Integer('Color Index')
    active = fields.Boolean(default=True, help="Set active to false to hide the Account Tag without removing it.")
    country_id = fields.Many2one(string="Country", comodel_name='res.country', help="Country for which this tag is available, when applied on taxes.")
    # If the tag was generated by a report line for the `tax_tags` engine, these fields allow getting
    # the sign of the report line used to display the amount.
    # If balance_negate is true then it means we use `-balance` instead of `balance`
    report_expression_id = fields.Many2one('account.report.expression', compute='_compute_report_expression_id')
    balance_negate = fields.Boolean(compute='_compute_report_expression_id')

    _name_uniq = models.Constraint(
        'unique(name, applicability, country_id)',
        'A tag with the same name and applicability already exists in this country.',
    )

    @api.depends('applicability', 'country_id')
    @api.depends_context('company')
    def _compute_display_name(self):
        if not self.env.company.multi_vat_foreign_country_ids:
            return super()._compute_display_name()

        for tag in self:
            name = tag.name
            if tag.applicability == "taxes" and tag.country_id and tag.country_id != self.env.company.account_fiscal_country_id:
                name = _("%(tag)s (%(country_code)s)", tag=tag.name, country_code=tag.country_id.code)
            tag.display_name = name

    @api.depends('name')
    def _compute_report_expression_id(self):
        query = self._search([('id', 'in', self.ids)])
        id2expression = {tag_id: vals for tag_id, *vals in self.env.execute_query(query.select(
            SQL.identifier(query.table, 'id'),
            self._field_to_sql(query.table, 'report_expression_id', query),
            self._field_to_sql(query.table, 'balance_negate', query),
        ))}
        for tag in self:
            tag.report_expression_id, tag.balance_negate = id2expression.get(tag._origin.id, (False, False))

    def _field_to_sql(self, alias: str, field_expr: str, query: (Query | None) = None) -> SQL:
        if field_expr in ('report_expression_id', 'balance_negate'):
            rhs_alias = query.make_alias(alias, 'expression')
            if rhs_alias not in query._tables:
                query.add_join(
                    kind='LEFT JOIN',
                    alias=rhs_alias,
                    table='account_report_expression',
                    condition=SQL(
                        "%s->>'en_US' = LTRIM(%s, '-')",
                        SQL.identifier(alias, 'name'),
                        SQL.identifier(rhs_alias, 'formula'),
                    ),
                )
            if field_expr == 'report_expression_id':
                return SQL.identifier(rhs_alias, 'id')
            if field_expr == 'balance_negate':
                return SQL("STARTS_WITH(%s, '-')", SQL.identifier(rhs_alias, 'formula'))
        return super()._field_to_sql(alias, field_expr, query)

    @api.model_create_multi
    def create(self, vals_list):
        tags = super().create(vals_list)
        if tax_tags := tags.filtered(lambda tag: tag.applicability == 'taxes'):
            self._translate_tax_tags(tag_ids=tax_tags.ids)
        return tags

    @api.model
    def _get_tax_tags(self, tag_name, country_id):
        """ Returns all the tax tags corresponding to the tag name given in parameter
        in the specified country.
        """
        domain = self._get_tax_tags_domain(tag_name, country_id)
        original_lang = self.env.context.get('lang', 'en_US')
        rslt_tags = self.env['account.account.tag'].with_context(active_test=False, lang='en_US').search(domain)
        return rslt_tags.with_context(lang=original_lang)  # Restore original language, in case the name of the tags needs to be shown/modified

    @api.model
    def _get_tax_tags_domain(self, formula, country_id):
        """ Returns a domain to search for all the tax tags corresponding to the formula given in parameter
        in the specified country.
        """
        return [
            ('name', '=', formula.lstrip('-')),
            ('country_id', '=', country_id),
            ('applicability', '=', 'taxes'),
        ]

    def _get_related_tax_report_expressions(self):
        if not self:
            return self.env['account.report.expression']

        return self.env['account.report.expression'].search(Domain('engine', '=', 'tax_tags') & Domain.OR(
            (
                Domain('report_line_id.report_id.country_id', '=', record.country_id.id)
                & Domain('formula', 'in', (record.name, '-' + record.name))
            )
            for record in self
        ))

    @api.ondelete(at_uninstall=False)
    def _unlink_except_master_tags(self):
        master_xmlids = [
            "account_tag_operating",
            "account_tag_financing",
            "account_tag_investing",
        ]
        for master_xmlid in master_xmlids:
            master_tag = self.env.ref(f"account.{master_xmlid}", raise_if_not_found=False)
            if master_tag and master_tag in self:
                raise UserError(_("You cannot delete this account tag (%s), it is used on the chart of account definition.", master_tag.name))

    def _translate_tax_tags(self, langs=None, tag_ids=None):
        """Translate tax tags having the same name as report lines."""
        langs = langs or (code for code, _name in self.env['res.lang'].get_installed() if code != 'en_US')
        for lang in langs:
            self.env.cr.execute(SQL(
                """
                UPDATE account_account_tag tag
                   SET name = tag.name || jsonb_build_object(%(lang)s, substring(tag.name->>'en_US' FOR 1) || (report_line.name->>%(lang)s))
                  FROM account_report_line report_line
                  JOIN account_report report ON report.id = report_line.report_id
                 WHERE tag.applicability = 'taxes'
                   AND tag.country_id = report.country_id
                   AND tag.name->>'en_US' = substring(tag.name->>'en_US' FOR 1) || (report_line.name->>'en_US')
                   AND tag.name->>%(lang)s != substring(tag.name->>'en_US' FOR 1) || (report_line.name->>%(lang)s)
                   %(and_tag_ids)s
                """,
                lang=lang,
                and_tag_ids=SQL('AND tag.id IN %s', tuple(tag_ids)) if tag_ids else SQL(''),
            ))
