import logging

from odoo import _, api, fields, models
from odoo.exceptions import UserError

from .avatax_rest_api import AvaTaxRESTService

_logger = logging.getLogger(__name__)


class ExemptionCode(models.Model):
    _name = "exemption.code"
    _description = "Exemption Code"

    name = fields.Char(required=True)
    code = fields.Char()

    @api.depends("name", "code")
    def name_get(self):
        def name(r):
            return r.code and "({}) {}".format(r.code, r.name) or r.name

        return [(r.id, name(r)) for r in self]


class AvalaraSalestax(models.Model):
    _name = "avalara.salestax"
    _description = "AvaTax Configuration"
    _rec_name = "account_number"

    @api.model
    def _get_avatax_supported_countries(self):
        """Returns the countries supported by AvaTax Address Validation Service."""
        return self.env["res.country"].search([("code", "in", ["US", "CA"])])

    account_number = fields.Char(
        name="Account ID", required=True, help="Account Number provided by AvaTax"
    )
    license_key = fields.Char(required=True, help="License Key provided by AvaTax")
    service_url = fields.Selection(
        [
            ("https://rest.avatax.com/api/v2", "Production (REST API)"),
            ("https://sandbox-rest.avatax.com/api/v2", "Sandbox (REST API)"),
        ],
        string="Service URL",
        default="https://rest.avatax.com/api/v2",
        help="The url to connect with",
    )
    request_timeout = fields.Integer(
        default=300,
        help="Defines AvaTax request time out length"
        ", AvaTax best practices prescribes default setting of 300 seconds",
    )
    company_code = fields.Char(
        default="DEFAULT",
        required=True,
        help="The company code as defined in the Admin Console of AvaTax",
    )
    logging = fields.Boolean(
        "Log API Requests",
        help="Enables detailed AvaTax transaction logging within application",
    )
    result_in_uppercase = fields.Boolean(
        "Return validation results in upper case",
        help="The address validation results are returned in in upper case",
    )
    disable_address_validation = fields.Boolean(
        help="Disables the ability to perform address validation"
    )
    validation_on_save = fields.Boolean(
        "Automatic Address Validation",
        help="Automatically validates addresses when they are created or modified",
    )
    force_address_validation = fields.Boolean(
        "Require Validated Addresses",
        help="Only compute taxes if addresses were validated by the Avatax service",
    )
    auto_generate_customer_code = fields.Boolean(
        "Automatically generate missing customer code",
        default=True,
        help="This will generate customer code for the customer used in the "
        "transaction, if it doesn't have one already. "
        "Each code is unique per customer.  "
        "When this is disabled, you will have to manually go to each customer "
        "and manually generate their customer code.  "
        "This is required for Avatax and is only generated one time.",
    )
    disable_tax_calculation = fields.Boolean(
        "Disable AvaTax Calculation",
        help="No tax calculation requests will be sent to the AvaTax web service.",
    )
    # TODO: Control - Disable Document Recording
    # In order for this connector to be used in conjunction
    # with other integrations to AvaTax, the user must be able to control which connector
    # is used for recording documents to AvaTax.
    # From a technical standpoint, simply use DocType: 'SalesOrder' on all calls
    # and suppress any non-getTax calls (i.e. cancelTax, postTax).
    disable_tax_reporting = fields.Boolean(
        "Disable Document Recording/Commiting",
        help="No transactions will be recorded (commited) to the Avatax service.",
    )
    country_ids = fields.Many2many(
        "res.country",
        "avalara_salestax_country_rel",
        "avalara_salestax_id",
        "country_id",
        "Countries",
        default=_get_avatax_supported_countries,
        help="Countries where address validation will be used",
    )
    active = fields.Boolean(
        default=True,
        help="Uncheck the active field to hide the record",
    )
    company_id = fields.Many2one(
        "res.company",
        "Company",
        required=True,
        default=lambda self: self.env.company,
        help="Company which has subscribed to the AvaTax service",
    )
    company_partner_id = fields.Many2one(
        string="Company Address",
        related="company_id.partner_id",
    )
    upc_enable = fields.Boolean(
        "Enable UPC Taxability",
        help="Allows ean13 to be reported in place of Item Reference"
        " as upc identifier.",
    )
    invoice_calculate_tax = fields.Boolean(
        "Auto Calculate Tax on Invoice Save",
        help="Automatically triggers API to calculate tax If changes made on"
        "Invoice's warehouse_id, tax_on_shipping_address, "
        "Invoice line's price_unit, discount, quantity",
    )
    use_so_partner_id = fields.Boolean(
        string="Use Sale Customer Code on Invoice",
        help="Use Sales Order's Customer field to determine Taxable "
        "Status on the Customer Invoice. If no Sales Order exists, "
        "Customer field on the invoice form view will be used instead",
    )
    hide_exemption = fields.Boolean(
        "Hide Exemption & Tax Based on shipping address",
        default=False,
        help="Uncheck the this field to show exemption fields on SO/Invoice form view. "
        "Also, it will show Tax based on shipping address button",
    )
    # TODO: add option to Display Prices with Tax Included
    # Enabled the tax inclusive flag in the GetTax Request.

    # constraints on uniq records creation with account_number and company_id
    _sql_constraints = [
        (
            "code_company_uniq",
            "unique (company_code)",
            "Avalara setting is already available for this company code",
        ),
        (
            "account_number_company_uniq",
            "unique (account_number, company_id)",
            "The account number must be unique per company!",
        ),
    ]

    def get_avatax_rest_service(self):
        self.ensure_one()
        if self.disable_tax_calculation:
            _logger.info(
                "Avatax tax calculation is disabled, skipping Avatax API contact."
            )
            return False
        return AvaTaxRESTService(
            self.account_number,
            self.license_key,
            self.service_url,
            self.request_timeout,
            self.logging,
            config=self,
        )

    def create_transaction(
        self,
        doc_date,
        doc_code,
        doc_type,
        partner,
        ship_from_address,
        shipping_address,
        lines,
        user=None,
        exemption_number=None,
        exemption_code_name=None,
        commit=False,
        invoice_date=None,
        reference_code=None,
        location_code=None,
        is_override=None,
        currency_id=None,
        ignore_error=None,
        log_to_record=False,
    ):
        self.ensure_one()
        avatax_config = self

        currency_code = self.env.company.currency_id.name
        if currency_id:
            currency_code = currency_id.name

        if not partner.customer_code:
            if not avatax_config.auto_generate_customer_code:
                raise UserError(
                    _(
                        "Customer Code for customer %(partner.name)s not defined.\n\n  "
                        "You can edit the Customer Code in customer profile. "
                        'You can fix by clicking "Generate Customer Code" '
                        "button in the customer contact information"
                    )
                )
            else:
                partner.generate_cust_code()

        if not shipping_address:
            raise UserError(
                _("There is no source shipping address defined for partner %s.")
                % partner.name
            )

        if not ship_from_address:
            raise UserError(_("There is no Company address defined."))

        if avatax_config.validation_on_save:
            for address in [partner, shipping_address, ship_from_address]:
                if not address.date_validation:
                    address.multi_address_validation(validation_on_save=True)

        # this condition is required, in case user select force address validation
        # on AvaTax API Configuration
        if (
            avatax_config.force_address_validation
            and not avatax_config.disable_address_validation
        ):
            if not shipping_address.date_validation:
                raise UserError(
                    _(
                        "Please validate the shipping address for the partner %(partner.name)s."
                    )
                )

            # if not avatax_config.address_validation:
            if not ship_from_address.date_validation:
                raise UserError(_("Please validate the origin warehouse address."))

        if avatax_config.disable_tax_calculation:
            _logger.info(
                "Avatax tax calculation is disabled. Skipping %s %s.",
                doc_code,
                doc_type,
            )
            return False

        if commit and avatax_config.disable_tax_reporting:
            _logger.warning(
                _("Avatax commiting document %s, but it tax reporting is disabled."),
                doc_code,
            )

        avatax = self.get_avatax_rest_service()
        result = avatax.get_tax(
            avatax_config.company_code,
            doc_date,
            doc_type,
            partner.customer_code,
            doc_code,
            ship_from_address,
            shipping_address,
            lines,
            exemption_number,
            exemption_code_name,
            user and user.name or None,
            commit and not avatax_config.disable_tax_reporting,
            invoice_date,
            reference_code,
            location_code,
            currency_code,
            partner.vat or None,
            is_override,
            ignore_error=ignore_error,
            log_to_record=log_to_record,
        )
        return result

    def commit_transaction(self, doc_code, doc_type):
        self.ensure_one()
        result = False
        if not self.disable_tax_reporting:
            avatax = self.get_avatax_rest_service()
            result = avatax.call(
                "commit_transaction", self.company_code, doc_code, {"commit": True}
            )
        return result

    def void_transaction(self, doc_code, doc_type):
        if self:
            self.ensure_one()
            result = False
            if not self.disable_tax_reporting:
                avatax = self.get_avatax_rest_service()
                result = avatax.call(
                    "void_transaction",
                    self.company_code,
                    doc_code,
                    {"code": "DocVoided"},
                )
            return result

    def unvoid_transaction(self, doc_code, doc_type):
        self.ensure_one()
        result = False
        if not self.disable_tax_reporting:
            avatax = self.get_avatax_rest_service()
            result = avatax.call("unvoid_transaction", self.company_code, doc_code)
        return result

    def ping(self):
        client = AvaTaxRESTService(config=self)
        client.ping()
        return True
