import logging
from decimal import Decimal
from urllib.error import URLError
from xml.sax import SAXParseException

import stdnum.eu.vat
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.utils.html import format_html
from requests.exceptions import ConnectionError, Timeout
from zeep.exceptions import Fault, TransportError, XMLSyntaxError

from plans.taxation import TaxationPolicy
from plans.taxation.tedb_client import TEDBClient
from plans.utils import country_code_transform

logger = logging.getLogger("plans.taxation.eu.vies")


class EUTaxationPolicy(TaxationPolicy):
    """
    This taxation policy should be correct for all EU countries. It uses following rules:
        * if issuer country is not in EU - assert error,
        * for buyer of the same country as issuer - return issuer tax,
        * for company buyer from EU (with VIES) returns VAT n/a reverse charge,
        * for non-company buyer from EU returns VAT from buyer country,
        * for non-EU buyer return VAT n/a.

    This taxation policy was updated at 1 Jan 2015 after new UE VAT regulations. You should also probably
    register in MOSS system.

    VAT rates are retrieved from the European Commission's TEDB service when possible,
    with fallback to static rates table for reliability.
    """

    # Standard VAT rates for EU countries
    # Updated: November 2025
    # Note: VAT rates change frequently - verify current rates for production use

    EU_COUNTRIES_VAT = {
        "BE": Decimal("21"),  # Belgium
        "BG": Decimal("20"),  # Bulgaria
        "CZ": Decimal("21"),  # Czech Republic
        "DK": Decimal("25"),  # Denmark
        "DE": Decimal("19"),  # Germany
        "EE": Decimal("24"),  # Estonia (increased to 24% in July 2025)
        "EL": Decimal("24"),  # Greece
        "ES": Decimal("21"),  # Spain
        "FR": Decimal("20"),  # France
        "HR": Decimal("25"),  # Croatia
        "IE": Decimal("23"),  # Ireland
        "IT": Decimal("22"),  # Italy
        "CY": Decimal("19"),  # Cyprus
        "LV": Decimal("21"),  # Latvia
        "LT": Decimal("21"),  # Lithuania
        "LU": Decimal("17"),  # Luxembourg
        "HU": Decimal("27"),  # Hungary
        "MT": Decimal("18"),  # Malta
        "NL": Decimal("21"),  # Netherlands
        "AT": Decimal("20"),  # Austria
        "PL": Decimal("23"),  # Poland
        "PT": Decimal("23"),  # Portugal
        "RO": Decimal("21"),  # Romania (increased from 19% in Aug 2025)
        "SI": Decimal("22"),  # Slovenia
        "SK": Decimal("23"),  # Slovakia (increased from 20% in Jan 2025)
        "FI": Decimal("25.5"),  # Finland (increased from 24% in Sep 2024)
        "SE": Decimal("25"),  # Sweden
        # 'GB': Decimal('20'),  # United Kingdom (Great Britain)
    }

    @classmethod
    def is_in_EU(cls, country_code):
        return country_code_transform(country_code).upper() in cls.EU_COUNTRIES_VAT

    @classmethod
    def _get_tedb_client(cls):
        """Get TEDB client instance, cached as class attribute."""
        if not hasattr(cls, "_tedb_client"):
            cls._tedb_client = TEDBClient()
        return cls._tedb_client

    @classmethod
    def _get_vat_rate_from_tedb(cls, country_code):
        """
        Get VAT rate from TEDB service with fallback to static table.

        Args:
            country_code: ISO 2-letter country code

        Returns:
            Decimal VAT rate or None if not available
        """
        try:
            tedb_client = cls._get_tedb_client()
            rate = tedb_client.get_vat_rate(country_code)
            if rate is not None:
                logger.info(f"Using TEDB VAT rate for {country_code}: {rate}")
                return rate
        except (ConnectionError, Timeout, Fault, TransportError, XMLSyntaxError) as e:
            logger.warning(f"TEDB service unavailable for {country_code}: {e}")

        # Fallback to static table
        try:
            rate = cls.EU_COUNTRIES_VAT[country_code]
            logger.info(f"Using fallback VAT rate for {country_code}: {rate}")
            return rate
        except KeyError:
            logger.error(f"No VAT rate available for {country_code}")
            return None

    @classmethod
    def get_default_tax(cls):
        issuer_country_code = cls.get_issuer_country_code()
        issuer_country_code = country_code_transform(issuer_country_code)

        # Try TEDB first, then fallback to static table
        rate = cls._get_vat_rate_from_tedb(issuer_country_code)
        if rate is not None:
            return rate

        raise ImproperlyConfigured(
            "EUTaxationPolicy requires that issuer country is in EU"
        )

    @classmethod
    def get_tax_rate(cls, tax_id, country_code, request=None):
        """
        returns tax rate and if the request was successful.
        """
        country_code = country_code_transform(country_code)
        issuer_country_code = cls.get_issuer_country_code()
        if not cls.is_in_EU(issuer_country_code):
            raise ImproperlyConfigured(
                "EUTaxationPolicy requires that issuer country is in EU"
            )

        if not tax_id and not country_code:
            # No vat id, no country
            return cls.get_default_tax(), True

        elif not tax_id and country_code:
            # Customer is not a company, we know his country

            if cls.is_in_EU(country_code):
                # Customer (private person) is from a EU
                # Customer pays his VAT rate
                rate = cls._get_vat_rate_from_tedb(country_code)
                if rate is not None:
                    return rate, True
                return cls.EU_COUNTRIES_VAT[country_code], True
            else:
                # Customer (private person) not from EU
                # VAT n/a
                return None, True

        else:
            # Customer is company, we now country and vat id

            if country_code.upper() == issuer_country_code.upper():
                # Company is from the same country as issuer
                # Normal tax
                return cls.get_default_tax(), True

            if cls.is_in_EU(country_code):
                # Company is from other EU country
                try:
                    vies_result = bool(stdnum.eu.vat.check_vies(tax_id)["valid"])
                    logger.info("TAX_ID=%s RESULT=%s" % (tax_id, vies_result))
                    if tax_id and vies_result:
                        # Company is registered in VIES
                        # Charge back
                        return None, True
                    else:
                        rate = cls._get_vat_rate_from_tedb(country_code)
                        if rate is not None:
                            return rate, True
                        return cls.EU_COUNTRIES_VAT[country_code], True
                except (
                    Fault,
                    TransportError,
                    XMLSyntaxError,
                    stdnum.exceptions.InvalidComponent,
                    ConnectionError,
                    URLError,
                    SAXParseException,
                    TimeoutError,
                ) as e:
                    # If we could not connect to VIES or the VAT ID is incorrect
                    if request:
                        messages.warning(
                            request,
                            format_html(
                                "There was an error during determining validity of your VAT ID.<br/>"
                                "If you think, you have european VAT ID and should not be taxed, "
                                "please try resaving billing info later.<br/>"
                                "<br/>"
                                "European VAT Information Exchange System throw following error: {}",
                                e,
                            ),
                        )
                    logger.exception("TAX_ID=%s" % (tax_id))
                    rate = cls._get_vat_rate_from_tedb(country_code)
                    if rate is not None:
                        return rate, False
                    return cls.EU_COUNTRIES_VAT[country_code], False
            else:
                # Company is not from EU
                # VAT n/a
                return None, True
