from __future__ import annotations

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

from ccma.domain.models import ContributionData

CENT = Decimal("0.01")

CLAIM_STATUS_LABELS = {
    "open": "OFFEN",
    "partially_paid": "TEILBEZAHLT",
    "paid": "BEZAHLT",
    "overpaid": "ÜBERZAHLT",
    "overdue": "ÜBERFÄLLIG",
    "cancelled": "STORNIERT",
}


def decimal_value(value: Any, field_name: str = "Betrag") -> Decimal:
    text = str(value).strip().replace(",", ".")
    try:
        return Decimal(text).quantize(CENT, rounding=ROUND_HALF_UP)
    except (InvalidOperation, ValueError) as exc:
        raise ValueError(f"{field_name} ist kein gültiger Geldbetrag.") from exc


def money_text(value: Decimal | str) -> str:
    return f"{decimal_value(value):.2f}"


def claim_items(claim: dict[str, Any]) -> list[dict[str, Any]]:
    items = claim.get("items")
    if isinstance(items, list) and items:
        return items
    amount = decimal_value(claim.get("amount", "0"))
    return [
        {
            "item_id": "legacy-base",
            "type": "base",
            "description": str(claim.get("title") or "Forderung"),
            "quantity": "1.00",
            "unit_price": money_text(amount),
            "amount": money_text(amount),
        }
    ]


def materialize_claim_items(claim: dict[str, Any]) -> list[dict[str, Any]]:
    if not isinstance(claim.get("items"), list) or not claim["items"]:
        claim["items"] = claim_items(claim)
    return claim["items"]


def claim_total(claim: dict[str, Any]) -> Decimal:
    return sum((decimal_value(item.get("amount", "0")) for item in claim_items(claim)), Decimal("0"))


def allocated_total(data: ContributionData, claim_id: str) -> Decimal:
    return sum(
        (
            decimal_value(allocation.get("amount", "0"))
            for allocation in data.allocations
            if str(allocation.get("claim_id", "")) == claim_id
        ),
        Decimal("0"),
    )


def payment_allocated_total(data: ContributionData, payment_id: str) -> Decimal:
    return sum(
        (
            decimal_value(allocation.get("amount", "0"))
            for allocation in data.allocations
            if str(allocation.get("payment_id", "")) == payment_id
        ),
        Decimal("0"),
    )


def claim_balance(data: ContributionData, claim: dict[str, Any]) -> Decimal:
    return (claim_total(claim) - allocated_total(data, str(claim.get("claim_id", "")))).quantize(CENT)


def claim_status(data: ContributionData, claim: dict[str, Any], *, today: date | None = None) -> str:
    if str(claim.get("status", "")) == "cancelled":
        return "cancelled"
    total = claim_total(claim)
    paid = allocated_total(data, str(claim.get("claim_id", "")))
    balance = total - paid
    if balance < 0:
        return "overpaid"
    if balance == 0:
        return "paid"
    if paid > 0:
        return "partially_paid"
    try:
        due = date.fromisoformat(str(claim.get("due_date", "")))
    except ValueError:
        due = None
    if due and due < (today or date.today()):
        return "overdue"
    return "open"
