"""
Paydown plan monitor — deviation detection.

Boundary 5 enforced: no Flask context, no direct DB session.
Inputs are plain Python objects (AmortizationResult + list of dicts).
"""
from __future__ import annotations

import datetime
from dataclasses import dataclass
from decimal import Decimal
from typing import Any

_DEVIATION_THRESHOLD = Decimal("1.05")  # 5% over planned
_STALE_DAYS = 30


@dataclass
class CardMonitorStatus:
    account_id: int
    account_name: str
    planned_balance: Decimal
    actual_balance: Decimal
    variance: Decimal          # actual - planned (positive = behind plan)
    is_behind: bool            # actual > planned * 1.05
    last_update_date: datetime.date | None
    is_stale: bool             # last update > 30 days ago (or never updated)
    projected_payoff_date: str


def get_monitor_statuses(
    result,                        # AmortizationResult
    balance_updates: list[dict],   # [{"account_id": int, "balance": Decimal, "updated_at": datetime}]
    reference_date: datetime.date | None = None,
) -> list[CardMonitorStatus]:
    """
    Compute per-card monitor status for the current month.

    result: AmortizationResult from amortization.py
    balance_updates: list of dicts with account_id, balance, updated_at — all updates ever recorded
    reference_date: the date to use for "this month" comparison (defaults to today)
    """
    today = reference_date or datetime.date.today()

    # Find how many months have elapsed since the plan was activated
    # We use months_since_activation to index into per_card_schedule
    # For simplicity: months elapsed = difference in months between today and plan start
    # The plan_start is not passed here — we use the first schedule row's date as approximation
    # Actually: the schedule rows are indexed 1-based by month number
    # We need to know which month number corresponds to "now"

    statuses = []

    # Build a lookup: account_id -> most recent balance update
    latest_update: dict[int, dict] = {}
    for upd in balance_updates:
        acct_id = upd["account_id"]
        if acct_id not in latest_update or upd["updated_at"] > latest_update[acct_id]["updated_at"]:
            latest_update[acct_id] = upd

    for card_data in result.per_card_schedule:
        acct_id = card_data["account_id"]
        schedule = card_data["schedule"]

        if not schedule:
            continue

        # Find the planned balance for "current month" —
        # the schedule row whose date is closest to today (within same month or latest past row)
        planned_balance = _planned_balance_for_date(schedule, today)

        # Actual balance: most recent update, or starting balance from schedule row 0
        update = latest_update.get(acct_id)
        if update:
            actual_balance = Decimal(str(update["balance"]))
            last_update_date = (
                update["updated_at"].date()
                if hasattr(update["updated_at"], "date")
                else update["updated_at"]
            )
        else:
            actual_balance = None
            last_update_date = None

        # Stale if no update, or last update > 30 days ago
        if last_update_date is None:
            is_stale = True
        else:
            is_stale = (today - last_update_date).days > _STALE_DAYS

        # Deviation: only computable when we have an actual balance
        if actual_balance is not None and planned_balance > Decimal("0"):
            # Use exact Decimal comparison (no rounding): actual > planned * 1.05
            is_behind = actual_balance > planned_balance * _DEVIATION_THRESHOLD
        else:
            is_behind = False

        if actual_balance is None:
            actual_balance = planned_balance  # display placeholder
        variance = actual_balance - planned_balance

        statuses.append(CardMonitorStatus(
            account_id=acct_id,
            account_name=card_data["account_name"],
            planned_balance=planned_balance,
            actual_balance=actual_balance,
            variance=variance,
            is_behind=is_behind,
            last_update_date=last_update_date,
            is_stale=is_stale,
            projected_payoff_date=card_data["payoff_date"],
        ))

    return statuses


def _planned_balance_for_date(
    schedule: list[dict],
    target: datetime.date,
) -> Decimal:
    """Return the planned end-of-month balance closest to target date."""
    if not schedule:
        return Decimal("0")

    # Find the row whose date <= target (latest such row)
    # Schedule dates are end-of-month payment dates
    best = schedule[0]
    for row in schedule:
        row_date = datetime.date.fromisoformat(row["date"])
        if row_date <= target:
            best = row
        else:
            break

    return Decimal(best["balance"])
