import logging
import re

from odoo import api, models, _

from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)


class ResPartner(models.Model):
    _inherit = "res.partner"

    @api.constrains("vat", "l10n_latam_identification_type_id")
    def check_vat(self):
        # EXTEND account/base_vat
        """ Add validation of UY document types CI and NIE """
        ci_nie_types = self.filtered(
            lambda p: p.l10n_latam_identification_type_id.l10n_uy_dgi_code in ("1", "3")
                      and p.l10n_latam_identification_type_id.country_id.code == "UY" and p.vat)
        for partner in ci_nie_types:
            if not partner._l10n_uy_ci_nie_is_valid():
                raise ValidationError(self._l10n_uy_build_vat_error_message(partner))
        return super(ResPartner, self - ci_nie_types).check_vat()

    @api.model
    def _l10n_uy_build_vat_error_message(self, partner):
        """ Similar to _build_vat_error_message but using latam doc type name instead of vat_label
        NOTE: maybe can be implemented in master to l10n_latam_base for the use of different doc types """
        vat_label = _("CI/NIE")
        expected_format = _("3:402.010-2 or 93:402.010-1 (CI or NIE)")

        # Catch use case where the record label is about the public user (name: False)
        if partner.name:
            msg = "\n" + _(
                "The %(vat_label)s number [%(wrong_vat)s] for %(partner_label)s does not seem to be valid."
                "\nNote: the expected format is %(expected_format)s",
                vat_label=vat_label,
                wrong_vat=partner.vat,
                partner_label=_("partner [%s]", partner.name),
                expected_format=expected_format,
            )
        else:
            msg = "\n" + _(
                "The %(vat_label)s number [%(wrong_vat)s] does not seem to be valid."
                "\nNote: the expected format is %(expected_format)s",
                vat_label=vat_label,
                wrong_vat=partner.vat,
                expected_format=expected_format,
            )
        return msg

    def _l10n_uy_ci_nie_is_valid(self):
        """ Check if the partner's CI or NIE number is a valid one.

        CI:
            1) The ID number is taken up to the second to last position, that is, the first 6 or 7 digits.
            2) Each digit is multiplied by a different factor starting from right to left, the factors are:
                2, 9, 8, 7, 6, 3, 4.
            3) The products obtained are added:
            4) The base module 10 is calculated on this result to obtain the check digit, expressed in another way,
            the next number ending in zero is taken that follows the result of the addition (for the example
            would be 60) subtracting the sum itself: 60 - 59 = 1. The verification digit of the example ID is 1.

            NOTE: If the ID has fewer digits, it is preceded with zeros and the mechanism described above is applied

        NIE:
            The calculation for the NIE is the same as that used for the CI. The only difference is that we skip the
            first number

        Both algorithms where extracted from Uruware's Technical Manual (section 9.2 and 9.3)

        Return: False is not valid, True is valid
        """
        self.ensure_one()

        # The VAT must consist only numbers (format could have these characters ":., " we can skip them later)
        invalid_chars = re.findall(r"[^0-9:., \-]", self.vat)
        if invalid_chars:
            return False

        ci_nie_number = re.sub("[^0-9]", "", self.vat)

        # we get the validation digit, if NIE doc type we skip the first digit
        is_nie = self.l10n_latam_identification_type_id.l10n_uy_dgi_code == "1"
        verif_digit = int(ci_nie_number[-1])
        ci_nie_number = ci_nie_number[1:-1] if is_nie else ci_nie_number[0:-1]

        # If number is < 7 digits we add 0 to the left
        ci_nie_number = "%07d" % int(ci_nie_number)

        # If NIE > 7 digits is not valid
        if len(ci_nie_number) > 7:
            return False

        verification_vector = (2, 9, 8, 7, 6, 3, 4)
        num_sum = sum(int(ci_nie_number[i]) * verification_vector[i] for i in range(7))

        res = -num_sum % 10
        return res == verif_digit
