"""Multi-evidence and spatiotemporal consistency checks."""

from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal
from typing import Any

from .models import RiskEvidence, RiskGraphClaimSnapshot
from .signals import NormalizedRiskSignal, normalize_risk_signals

ZERO = Decimal("0")


def evaluate_claim_consistency(
    claim: RiskGraphClaimSnapshot,
) -> tuple[list[RiskEvidence], list[NormalizedRiskSignal]]:
    evidence: list[RiskEvidence] = []
    signals: list[NormalizedRiskSignal] = []

    if _has_location_mismatch(claim):
        evidence.append(
            RiskEvidence(
                code="location_mismatch_graph",
                title="Location mismatch graph",
                detail="Claim location and item location are not aligned.",
                source="spatiotemporal",
                score=64,
            )
        )
        signals.extend(normalize_risk_signals(["location_mismatch"], source="spatiotemporal"))

    amount_mismatch = _document_amount_mismatch(claim)
    if amount_mismatch:
        evidence.append(
            RiskEvidence(
                code="document_amount_mismatch",
                title="Document amount mismatch",
                detail="Claim amount and item amount sum are not aligned.",
                source="multi_evidence",
                score=72,
                metadata=amount_mismatch,
            )
        )
        signals.extend(
            normalize_risk_signals(
                [{"risk_signal": "document_expense_mismatch", "score": 72}],
                source="multi_evidence",
            )
        )

    invoice_count_mismatch = _invoice_count_mismatch(claim)
    if invoice_count_mismatch:
        evidence.append(
            RiskEvidence(
                code="invoice_count_mismatch",
                title="Invoice count mismatch",
                detail="Declared invoice count and attached invoice count are not aligned.",
                source="multi_evidence",
                score=62,
                metadata=invoice_count_mismatch,
            )
        )
        signals.extend(
            normalize_risk_signals(
                [{"risk_signal": "document_expense_mismatch", "score": 62}],
                source="multi_evidence",
            )
        )

    date_mismatch = _item_date_outside_claim_window(claim)
    if date_mismatch:
        evidence.append(
            RiskEvidence(
                code="date_outside_claim_window",
                title="Date outside claim window",
                detail="Item date is too far away from the claim occurrence date.",
                source="spatiotemporal",
                score=78,
                metadata=date_mismatch,
            )
        )
        signals.extend(normalize_risk_signals(["date_outside_trip"], source="spatiotemporal"))

    return evidence, signals


def _has_location_mismatch(claim: RiskGraphClaimSnapshot) -> bool:
    claim_location = _canonical_key(claim.location)
    if not claim_location or not claim.items:
        return False
    item_locations = {
        _canonical_key(item.item_location)
        for item in claim.items
        if str(item.item_location or "").strip()
    }
    if not item_locations:
        return False
    return any(location and location != claim_location for location in item_locations)


def _document_amount_mismatch(claim: RiskGraphClaimSnapshot) -> dict[str, str] | None:
    if not claim.items:
        return None
    claim_amount = _to_decimal(claim.amount)
    item_amount_sum = sum((_to_decimal(item.item_amount) for item in claim.items), ZERO)
    if claim_amount <= ZERO or item_amount_sum <= ZERO:
        return None
    difference = abs(claim_amount - item_amount_sum)
    tolerance = max(Decimal("1"), claim_amount * Decimal("0.02"))
    if difference <= tolerance:
        return None
    return {
        "claim_amount": str(claim_amount),
        "item_amount_sum": str(item_amount_sum),
        "difference": str(difference),
        "tolerance": str(tolerance),
    }


def _invoice_count_mismatch(claim: RiskGraphClaimSnapshot) -> dict[str, Any] | None:
    declared_count = int(claim.invoice_count or 0)
    if declared_count <= 0:
        return None
    invoice_ids = sorted(
        {
            str(item.invoice_id or "").strip()
            for item in claim.items
            if str(item.invoice_id or "").strip()
        }
    )
    actual_count = len(invoice_ids)
    if declared_count == actual_count:
        return None
    return {
        "declared_invoice_count": declared_count,
        "actual_invoice_count": actual_count,
        "invoice_ids": invoice_ids,
    }


def _item_date_outside_claim_window(claim: RiskGraphClaimSnapshot) -> dict[str, Any] | None:
    occurred_date = _date_from_value(claim.occurred_at)
    if occurred_date is None or not claim.items:
        return None
    mismatches: list[dict[str, Any]] = []
    for item in claim.items:
        item_date = _date_from_value(item.item_date)
        if item_date is None:
            continue
        distance_days = abs((item_date - occurred_date).days)
        if distance_days <= 7:
            continue
        mismatches.append(
            {
                "item_id": item.item_id,
                "item_date": item_date.isoformat(),
                "occurred_at": occurred_date.isoformat(),
                "distance_days": distance_days,
            }
        )
    return {"mismatches": mismatches} if mismatches else None


def _date_from_value(value: Any) -> date | None:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value.date()
    if isinstance(value, date):
        return value
    return None


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


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