"""
Tax calculation engine.

Usage from pricing.py:
    result = await calculate_order_tax(db, tenant_id, lines, shipping_total, country, province)
    result.total_tax        -> Decimal
    result.line_taxes       -> list[LineTax]
    result.shipping_tax     -> Decimal
    result.prices_include_tax -> bool
"""
import logging
from dataclasses import dataclass, field
from decimal import Decimal, ROUND_HALF_UP
from typing import Optional

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

TWO_PLACES = Decimal("0.01")


@dataclass
class LineTax:
    product_id: int
    variant_id: Optional[int]
    tax_rate: Decimal
    tax_amount: Decimal
    tax_name: str


@dataclass
class TaxResult:
    total_tax: Decimal = Decimal("0")
    line_taxes: list[LineTax] = field(default_factory=list)
    shipping_tax: Decimal = Decimal("0")
    shipping_tax_rate: Decimal = Decimal("0")
    prices_include_tax: bool = False


async def _load_tax_settings(db: AsyncSession, tenant_id: int):
    from app.plugins.tax.models import TaxSettings
    r = await db.execute(
        select(TaxSettings).where(TaxSettings.tenant_id == tenant_id)
    )
    return r.scalar_one_or_none()


async def _get_default_tax_class_id(db: AsyncSession, tenant_id: int) -> Optional[int]:
    from app.plugins.tax.models import TaxClass
    r = await db.execute(
        select(TaxClass.id).where(
            TaxClass.tenant_id == tenant_id,
            TaxClass.is_default == 1,
        )
    )
    return r.scalar_one_or_none()


async def _get_product_tax_class_id(db: AsyncSession, product_id: int) -> Optional[int]:
    from app.core.models.product import Product
    r = await db.execute(select(Product.tax_class_id).where(Product.id == product_id))
    return r.scalar_one_or_none()


async def _match_rates(
    db: AsyncSession,
    tenant_id: int,
    tax_class_id: int,
    country: str,
    province: str,
) -> list:
    from app.plugins.tax.models import TaxRate

    # Try exact province match first
    r = await db.execute(
        select(TaxRate).where(
            TaxRate.tenant_id == tenant_id,
            TaxRate.tax_class_id == tax_class_id,
            TaxRate.country == country.upper(),
            TaxRate.province == province,
        ).order_by(TaxRate.priority.desc())
    )
    rates = r.scalars().all()
    if rates:
        return rates

    # Fallback to country-wide (province is NULL or empty string)
    from sqlalchemy import or_
    r = await db.execute(
        select(TaxRate).where(
            TaxRate.tenant_id == tenant_id,
            TaxRate.tax_class_id == tax_class_id,
            TaxRate.country == country.upper(),
            or_(TaxRate.province.is_(None), TaxRate.province == ""),
        ).order_by(TaxRate.priority.desc())
    )
    return r.scalars().all()


def _calc_tax_for_amount(
    amount: Decimal,
    rates: list,
    prices_include_tax: bool,
    rounding_mode: str,
) -> tuple[Decimal, Decimal, str]:
    if not rates:
        return Decimal("0"), Decimal("0"), ""

    total_tax = Decimal("0")
    labels = []

    for rate_obj in rates:
        r = rate_obj.rate
        if r == 0:
            continue

        if rate_obj.compound:
            base = amount + total_tax
        else:
            base = amount

        if prices_include_tax:
            tax = base - (base / (1 + r))
        else:
            tax = base * r

        if rounding_mode == "line":
            tax = tax.quantize(TWO_PLACES, rounding=ROUND_HALF_UP)

        total_tax += tax
        labels.append(rate_obj.name)

    effective_rate = rates[0].rate if len(rates) == 1 else Decimal("0")
    label = " + ".join(labels)
    return effective_rate, total_tax, label


async def calculate_order_tax(
    db: AsyncSession,
    tenant_id: int,
    lines: list,
    shipping_total: Decimal,
    country: str,
    province: str,
) -> TaxResult:
    result = TaxResult()

    if not country:
        return result

    settings = await _load_tax_settings(db, tenant_id)
    if settings is None:
        return result

    result.prices_include_tax = bool(settings.prices_include_tax)
    default_class_id = await _get_default_tax_class_id(db, tenant_id)

    raw_taxes: list[Decimal] = []

    for line in lines:
        tax_class_id = await _get_product_tax_class_id(db, line.product_id)
        if tax_class_id is None:
            tax_class_id = default_class_id
        if tax_class_id is None:
            result.line_taxes.append(LineTax(
                product_id=line.product_id,
                variant_id=line.variant_id,
                tax_rate=Decimal("0"),
                tax_amount=Decimal("0"),
                tax_name="",
            ))
            raw_taxes.append(Decimal("0"))
            continue

        rates = await _match_rates(db, tenant_id, tax_class_id, country, province)
        effective_rate, tax_amount, label = _calc_tax_for_amount(
            line.line_total, rates, result.prices_include_tax, settings.rounding_mode,
        )
        result.line_taxes.append(LineTax(
            product_id=line.product_id,
            variant_id=line.variant_id,
            tax_rate=effective_rate,
            tax_amount=tax_amount,
            tax_name=label,
        ))
        raw_taxes.append(tax_amount)

    if settings.tax_shipping and shipping_total > 0:
        ship_class_id = settings.shipping_tax_class_id or default_class_id
        if ship_class_id:
            rates = await _match_rates(db, tenant_id, ship_class_id, country, province)
            result.shipping_tax_rate, result.shipping_tax, _ = _calc_tax_for_amount(
                shipping_total, rates, result.prices_include_tax, settings.rounding_mode,
            )
            raw_taxes.append(result.shipping_tax)

    if settings.rounding_mode == "total":
        result.total_tax = sum(raw_taxes).quantize(TWO_PLACES, rounding=ROUND_HALF_UP)
    else:
        result.total_tax = sum(raw_taxes)

    return result
