"""Profile baseline contracts for digital employee scans."""

from __future__ import annotations

from collections import defaultdict
from dataclasses import dataclass, field
from decimal import ROUND_CEILING, ROUND_FLOOR, Decimal
from typing import Any

from .models import ALGORITHM_VERSION, RiskGraphClaimSnapshot

ZERO = Decimal("0")
HUNDRED = Decimal("100")

BASELINE_ALGORITHM_VERSION = f"{ALGORITHM_VERSION}.profile_baselines.v1"
BASELINE_DIMENSIONS = ("employee", "department", "supplier", "expense_type")
SUPPLIER_ID_KEYS = ("supplier_id", "vendor_id", "merchant_id", "supplier_code")
SUPPLIER_NAME_KEYS = ("supplier_name", "vendor_name", "merchant_name", "supplier", "vendor", "merchant")


@dataclass(frozen=True, slots=True)
class ProfileBaselineBucket:
    dimension: str
    key: str
    label: str
    sample_size: int
    claim_count: int
    total_amount: Decimal
    average_amount: Decimal
    median_amount: Decimal
    p75_amount: Decimal
    p90_amount: Decimal
    claim_ids: list[str] = field(default_factory=list)

    def as_dict(self) -> dict[str, Any]:
        return {
            "dimension": self.dimension,
            "key": self.key,
            "label": self.label,
            "sample_size": self.sample_size,
            "claim_count": self.claim_count,
            "total_amount": _format_decimal(self.total_amount),
            "average_amount": _format_decimal(self.average_amount),
            "median_amount": _format_decimal(self.median_amount),
            "p75_amount": _format_decimal(self.p75_amount),
            "p90_amount": _format_decimal(self.p90_amount),
            "claim_ids": list(self.claim_ids),
        }


@dataclass(frozen=True, slots=True)
class ProfileBaselineSnapshot:
    algorithm_version: str
    buckets: list[ProfileBaselineBucket] = field(default_factory=list)

    @property
    def dimension_counts(self) -> dict[str, int]:
        counts = {dimension: 0 for dimension in BASELINE_DIMENSIONS}
        for bucket in self.buckets:
            counts[bucket.dimension] = counts.get(bucket.dimension, 0) + 1
        return counts

    def buckets_for(self, dimension: str) -> list[ProfileBaselineBucket]:
        return [bucket for bucket in self.buckets if bucket.dimension == dimension]

    def as_dict(self) -> dict[str, Any]:
        return {
            "algorithm_version": self.algorithm_version,
            "dimension_counts": self.dimension_counts,
            "bucket_count": len(self.buckets),
            "buckets": [bucket.as_dict() for bucket in self.buckets],
        }


class ProfileBaselineUpdater:
    def build_from_claims(
        self,
        claims: list[RiskGraphClaimSnapshot],
    ) -> ProfileBaselineSnapshot:
        grouped: dict[tuple[str, str], list[tuple[Decimal, str]]] = defaultdict(list)
        labels: dict[tuple[str, str], str] = {}

        for claim in claims:
            self._add_claim_rows(grouped, labels, claim)

        buckets = [
            _build_bucket(dimension, key, labels[(dimension, key)], rows)
            for (dimension, key), rows in grouped.items()
        ]
        buckets.sort(key=lambda item: (item.dimension, -item.total_amount, item.key))
        return ProfileBaselineSnapshot(
            algorithm_version=BASELINE_ALGORITHM_VERSION,
            buckets=buckets,
        )

    def _add_claim_rows(
        self,
        grouped: dict[tuple[str, str], list[tuple[Decimal, str]]],
        labels: dict[tuple[str, str], str],
        claim: RiskGraphClaimSnapshot,
    ) -> None:
        amount = _to_decimal(claim.amount)
        claim_id = claim.claim_id or claim.claim_no
        _add_row(
            grouped,
            labels,
            "employee",
            claim.employee_id or claim.employee_name,
            claim.employee_name or claim.employee_id,
            amount,
            claim_id,
        )
        _add_row(
            grouped,
            labels,
            "department",
            claim.department_id or claim.department_name,
            claim.department_name or claim.department_id,
            amount,
            claim_id,
        )
        _add_row(
            grouped,
            labels,
            "expense_type",
            claim.expense_type,
            claim.expense_type,
            amount,
            claim_id,
        )
        for supplier_key, supplier_label, supplier_amount in _supplier_rows(claim):
            _add_row(
                grouped,
                labels,
                "supplier",
                supplier_key,
                supplier_label,
                supplier_amount,
                claim_id,
            )


def _build_bucket(
    dimension: str,
    key: str,
    label: str,
    rows: list[tuple[Decimal, str]],
) -> ProfileBaselineBucket:
    amounts = [amount for amount, _claim_id in rows]
    total = sum(amounts, ZERO)
    sample_size = len(amounts)
    claim_ids = sorted({claim_id for _amount, claim_id in rows if claim_id})
    average = total / Decimal(sample_size) if sample_size else ZERO
    return ProfileBaselineBucket(
        dimension=dimension,
        key=key,
        label=label,
        sample_size=sample_size,
        claim_count=len(claim_ids),
        total_amount=total,
        average_amount=average,
        median_amount=_percentile(amounts, 50),
        p75_amount=_percentile(amounts, 75),
        p90_amount=_percentile(amounts, 90),
        claim_ids=claim_ids,
    )


def _add_row(
    grouped: dict[tuple[str, str], list[tuple[Decimal, str]]],
    labels: dict[tuple[str, str], str],
    dimension: str,
    key_source: Any,
    label_source: Any,
    amount: Decimal,
    claim_id: str,
) -> None:
    key = _canonical_key(key_source)
    if not key:
        return
    group_key = (dimension, key)
    labels.setdefault(group_key, str(label_source or key_source or key).strip() or key)
    grouped[group_key].append((amount, claim_id))


def _supplier_rows(claim: RiskGraphClaimSnapshot) -> list[tuple[str, str, Decimal]]:
    item_rows: list[tuple[str, str, Decimal]] = []
    for item in claim.items:
        supplier = _extract_supplier(item.metadata)
        if supplier is not None:
            item_rows.append((*supplier, _to_decimal(item.item_amount)))
    if item_rows:
        return item_rows

    supplier = _extract_supplier(claim.metadata) or _extract_supplier_from_flags(claim.risk_flags)
    if supplier is None:
        return []
    return [(*supplier, _to_decimal(claim.amount))]


def _extract_supplier(metadata: Any) -> tuple[str, str] | None:
    if not isinstance(metadata, dict):
        return None
    supplier_id = _first_text(metadata, SUPPLIER_ID_KEYS)
    supplier_name = _first_text(metadata, SUPPLIER_NAME_KEYS)
    key = supplier_id or supplier_name
    if not key:
        return None
    return key, supplier_name or supplier_id or key


def _extract_supplier_from_flags(flags: list[Any]) -> tuple[str, str] | None:
    for flag in flags or []:
        if not isinstance(flag, dict):
            continue
        supplier = _extract_supplier(flag) or _extract_supplier(flag.get("metadata"))
        if supplier is not None:
            return supplier
    return None


def _first_text(source: dict[str, Any], keys: tuple[str, ...]) -> str:
    for key in keys:
        value = str(source.get(key) or "").strip()
        if value:
            return value
    return ""


def _percentile(values: list[Decimal], percent: int) -> Decimal:
    normalized = sorted(value for value in values if value >= ZERO)
    if not normalized:
        return ZERO
    if len(normalized) == 1:
        return normalized[0]
    position = Decimal(len(normalized) - 1) * Decimal(percent) / HUNDRED
    lower = int(position.to_integral_value(rounding=ROUND_FLOOR))
    upper = int(position.to_integral_value(rounding=ROUND_CEILING))
    if lower == upper:
        return normalized[lower]
    fraction = position - Decimal(lower)
    return normalized[lower] + (normalized[upper] - normalized[lower]) * fraction


def _to_decimal(value: Any) -> Decimal:
    try:
        return Decimal(str(value or "0"))
    except Exception:
        return ZERO


def _format_decimal(value: Any) -> str:
    if not isinstance(value, Decimal):
        value = _to_decimal(value)
    return format(value.quantize(Decimal("0.0001")), "f").rstrip("0").rstrip(".") or "0"


def _canonical_key(value: Any) -> str:
    return "_".join(str(value or "").strip().lower().split())
