"""Customer-specific pricing engine for the B2B ordering portal.

The backend is the single source of truth for prices — the frontend never
computes a final price. :func:`resolve_price` returns a fully-attributed
:class:`PriceResolution` so every order line, and any future report, can explain
exactly which rule produced the number.

Resolution priority (highest wins):

1. **Quote-only** — the product requires a manual quote, or the customer has a
   ``quote`` price rule for it. No automatic price.
2. **Fixed / contract** — a :class:`CustomerProductPrice` for this customer +
   product (quantity tiers may refine it).
3. **Price list** — a :class:`PriceListItem` from the customer's assigned price
   list (quantity tiers may refine it).
4. **Base + discount** — the catalogue list price, optionally reduced by the
   customer's default discount percentage.
5. **Quote fallback** — no resolvable price ⇒ treated as quote-only.

All prices are GST-exclusive.
"""
from __future__ import annotations

from dataclasses import dataclass

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.ordering import (
    CatalogueProduct,
    CustomerPriceAssignment,
    CustomerProductPrice,
    PriceListItem,
    PriceTier,
)


@dataclass(frozen=True)
class PriceResolution:
    unit_price: float | None
    price_source: str  # one of ordering.PRICE_SOURCES
    price_rule_id: int | None
    discount_percent: float
    requires_quote: bool
    label: str

    def line_total(self, quantity: float) -> float | None:
        if self.unit_price is None:
            return None
        return round(self.unit_price * quantity, 4)


_SOURCE_LABELS = {
    "fixed": "Fixed price",
    "contract": "Contract price",
    "price_list": "Price list",
    "tiered": "Tiered price",
    "base": "List price",
    "quote": "Quote required",
}


def _best_tier_price(
    db: Session,
    *,
    customer_product_price_id: int | None = None,
    price_list_item_id: int | None = None,
    quantity: float,
) -> float | None:
    """Return the unit price of the highest qualifying quantity tier, if any."""
    stmt = select(PriceTier).where(PriceTier.min_quantity <= quantity)
    if customer_product_price_id is not None:
        stmt = stmt.where(PriceTier.customer_product_price_id == customer_product_price_id)
    elif price_list_item_id is not None:
        stmt = stmt.where(PriceTier.price_list_item_id == price_list_item_id)
    else:
        return None
    tiers = db.scalars(stmt.order_by(PriceTier.min_quantity.desc())).all()
    return tiers[0].unit_price if tiers else None


def _quote(label: str = "Quote required") -> PriceResolution:
    return PriceResolution(
        unit_price=None,
        price_source="quote",
        price_rule_id=None,
        discount_percent=0.0,
        requires_quote=True,
        label=label,
    )


def get_customer_assignment(
    db: Session, *, client_account_id: int
) -> CustomerPriceAssignment | None:
    return db.scalar(
        select(CustomerPriceAssignment).where(
            CustomerPriceAssignment.client_account_id == client_account_id
        )
    )


def resolve_price(
    db: Session,
    *,
    client_account_id: int,
    product: CatalogueProduct,
    quantity: float,
) -> PriceResolution:
    """Resolve the GST-exclusive unit price for a customer + product + quantity."""
    quantity = max(quantity, 0.0)

    # 1. Product-level manual-quote flag.
    if product.requires_quote:
        return _quote()

    # 2. Customer-specific fixed/contract/quote rule.
    cpp = db.scalar(
        select(CustomerProductPrice).where(
            CustomerProductPrice.client_account_id == client_account_id,
            CustomerProductPrice.product_id == product.id,
            CustomerProductPrice.active.is_(True),
        )
    )
    if cpp is not None:
        if cpp.rule_type == "quote":
            return _quote()
        tier_price = _best_tier_price(
            db, customer_product_price_id=cpp.id, quantity=quantity
        )
        if tier_price is not None:
            return PriceResolution(
                unit_price=round(tier_price, 4),
                price_source="tiered",
                price_rule_id=cpp.id,
                discount_percent=0.0,
                requires_quote=False,
                label=_SOURCE_LABELS["tiered"],
            )
        if cpp.unit_price is not None:
            source = "contract" if cpp.rule_type == "contract" else "fixed"
            return PriceResolution(
                unit_price=round(cpp.unit_price, 4),
                price_source=source,
                price_rule_id=cpp.id,
                discount_percent=0.0,
                requires_quote=False,
                label=_SOURCE_LABELS[source],
            )

    assignment = get_customer_assignment(db, client_account_id=client_account_id)

    # 3. Assigned price list.
    if assignment is not None and assignment.price_list_id is not None:
        pli = db.scalar(
            select(PriceListItem).where(
                PriceListItem.price_list_id == assignment.price_list_id,
                PriceListItem.product_id == product.id,
            )
        )
        if pli is not None:
            tier_price = _best_tier_price(
                db, price_list_item_id=pli.id, quantity=quantity
            )
            if tier_price is not None:
                return PriceResolution(
                    unit_price=round(tier_price, 4),
                    price_source="tiered",
                    price_rule_id=pli.id,
                    discount_percent=0.0,
                    requires_quote=False,
                    label=_SOURCE_LABELS["tiered"],
                )
            return PriceResolution(
                unit_price=round(pli.unit_price, 4),
                price_source="price_list",
                price_rule_id=pli.id,
                discount_percent=0.0,
                requires_quote=False,
                label=_SOURCE_LABELS["price_list"],
            )

    # 4. Base price (+ optional customer discount).
    if product.base_price is not None:
        discount = assignment.discount_percent if assignment else 0.0
        discount = min(max(discount, 0.0), 100.0)
        unit_price = round(product.base_price * (1.0 - discount / 100.0), 4)
        label = _SOURCE_LABELS["base"]
        if discount:
            label = f"List price less {discount:g}%"
        return PriceResolution(
            unit_price=unit_price,
            price_source="base",
            price_rule_id=product.id,
            discount_percent=discount,
            requires_quote=False,
            label=label,
        )

    # 5. No resolvable price ⇒ quote.
    return _quote("No price set — quote required")
