from datetime import date, timedelta

from ccma.domain.contributions import claim_balance, claim_status, money_text
from ccma.rules.api import RuleContext, task

RULE_ID = "reminder-due"
ORDER = 60

DEFAULT_POLICY = {
    "grace_days_after_due": 7,
    "levels": [
        {"level": 1, "name": "Zahlungserinnerung", "fee": "0.00", "payment_deadline_days": 14},
        {"level": 2, "name": "Erste Mahnung", "fee": "5.00", "payment_deadline_days": 14},
        {"level": 3, "name": "Zweite Mahnung", "fee": "5.00", "payment_deadline_days": 14},
    ],
}


def evaluate(context: RuleContext):
    policy = context.repository_config.get("reminder_policy") or DEFAULT_POLICY
    levels = sorted(policy.get("levels") or [], key=lambda value: int(value.get("level", 0)))
    actions = []
    for claim in context.contributions.claims:
        claim_id = str(claim.get("claim_id", ""))
        if not claim_id or claim_status(context.contributions, claim, today=context.today) not in {
            "open",
            "partially_paid",
            "overdue",
        }:
            continue
        if claim_balance(context.contributions, claim) <= 0 or _hold_is_active(claim, context.today):
            continue
        reminders = [
            item for item in context.contributions.reminders if str(item.get("claim_id", "")) == claim_id
        ]
        sent_levels = {
            int(item.get("level", 0)): item for item in reminders if str(item.get("status", "")) == "sent"
        }
        next_level = next(
            (definition for definition in levels if int(definition.get("level", 0)) not in sent_levels),
            None,
        )
        if not next_level:
            continue
        level = int(next_level.get("level", 0))
        trigger_date = _trigger_date(claim, sent_levels, level, policy)
        if not trigger_date or context.today < trigger_date:
            continue
        draft_exists = any(
            int(item.get("level", 0)) == level and str(item.get("status", "draft")) in {"draft", "generated"}
            for item in reminders
        )
        name = str(next_level.get("name") or f"Mahnung Stufe {level}")
        balance = money_text(claim_balance(context.contributions, claim))
        title = (
            f"{context.member.display_name}: Mahnungsentwurf wartet auf Versand"
            if draft_exists
            else f"{context.member.display_name}: {name} fällig"
        )
        detail = (
            f"Forderung: {claim.get('title', claim_id)}. Offener Betrag: {balance} EUR. "
            f"Mahnstufe {level}, vorgesehen Gebühr: {next_level.get('fee', '0.00')} EUR."
        )
        actions.append(
            task(
                rule_id=RULE_ID,
                member=context.member,
                key_suffix=f"{claim_id}:level-{level}",
                severity="warning",
                code="reminder_due",
                title=title,
                detail=detail,
                due_date=trigger_date,
            )
        )
    return actions


def _trigger_date(claim, sent_levels, level: int, policy) -> date | None:
    if level == 1:
        try:
            due_date = date.fromisoformat(str(claim.get("due_date", "")))
        except ValueError:
            return None
        return due_date + timedelta(days=int(policy.get("grace_days_after_due", 7)))
    previous = sent_levels.get(level - 1)
    if not previous:
        return None
    try:
        return date.fromisoformat(str(previous.get("payment_deadline", "")))
    except ValueError:
        return None


def _hold_is_active(claim, today: date) -> bool:
    hold = claim.get("dunning_hold") or {}
    if not hold.get("active"):
        return False
    until = hold.get("until")
    if not until:
        return True
    try:
        return date.fromisoformat(str(until)) >= today
    except ValueError:
        return True
