"""
Subscription detector — Story 10.4.

Scans transactions for recurring charges: same merchant, similar amounts (±$1),
on a recurring interval (weekly ~7d, monthly ~30d, annual ~365d).
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import date, timedelta
from decimal import Decimal

from sqlalchemy import func

from app.extensions import db
from app.models.transaction import Transaction


# Interval windows: (label, min_days, max_days)
_INTERVALS = [
    ("weekly",  5,  9),
    ("monthly", 26, 35),
    ("annual",  350, 380),
]
_AMOUNT_TOLERANCE = Decimal("1.00")


@dataclass
class DetectedSubscription:
    merchant: str
    estimated_amount: Decimal
    recurrence: str      # weekly / monthly / annual
    last_charge_date: str
    occurrence_count: int
    total_last_12mo: Decimal


def detect_subscriptions() -> list[DetectedSubscription]:
    """
    FR-3.3: Detect subscription-like transactions.

    Groups by merchant_normalized, then checks if charges appear at regular
    intervals with similar amounts.
    """
    today = date.today()
    one_year_ago = (today - timedelta(days=365)).isoformat()

    # Pull all non-credit transactions in the last 2 years grouped by merchant
    merchant_rows = (
        db.session.query(
            Transaction.merchant_normalized,
            func.count(Transaction.id).label("count"),
            func.sum(Transaction.amount).label("total"),
            func.max(Transaction.date).label("last_date"),
        )
        .filter(
            Transaction.is_credit == False,  # noqa: E712
            Transaction.date >= (today - timedelta(days=730)).isoformat(),
        )
        .group_by(Transaction.merchant_normalized)
        .having(func.count(Transaction.id) >= 2)
        .all()
    )

    subscriptions = []
    for row in merchant_rows:
        merchant = row.merchant_normalized or "Unknown"
        # Load individual transaction dates and amounts
        txns = (
            Transaction.query
            .filter(
                Transaction.merchant_normalized == merchant,
                Transaction.is_credit == False,  # noqa: E712
                Transaction.date >= (today - timedelta(days=730)).isoformat(),
            )
            .order_by(Transaction.date)
            .all()
        )

        if len(txns) < 2:
            continue

        recurrence = _detect_recurrence(txns)
        if recurrence is None:
            continue

        # Total in last 12 months
        total_12mo = sum(
            t.amount for t in txns if t.date >= one_year_ago
        )
        avg_amount = Decimal(str(row.total or 0)) / len(txns)

        subscriptions.append(DetectedSubscription(
            merchant=merchant,
            estimated_amount=avg_amount.quantize(Decimal("0.01")),
            recurrence=recurrence,
            last_charge_date=row.last_date,
            occurrence_count=len(txns),
            total_last_12mo=Decimal(str(total_12mo or 0)).quantize(Decimal("0.01")),
        ))

    return sorted(subscriptions, key=lambda s: s.merchant)


def _detect_recurrence(txns: list) -> str | None:
    """Check if transactions follow a regular interval. Returns label or None."""
    if len(txns) < 2:
        return None

    # Compute gaps in days between consecutive transactions with similar amounts
    gaps = []
    for i in range(1, len(txns)):
        a, b = txns[i - 1], txns[i]
        amount_diff = abs(Decimal(str(a.amount)) - Decimal(str(b.amount)))
        if amount_diff > _AMOUNT_TOLERANCE:
            continue
        try:
            d_a = date.fromisoformat(a.date)
            d_b = date.fromisoformat(b.date)
            gaps.append((d_b - d_a).days)
        except (ValueError, AttributeError):
            continue

    if not gaps:
        return None

    avg_gap = sum(gaps) / len(gaps)
    for label, min_days, max_days in _INTERVALS:
        if min_days <= avg_gap <= max_days:
            return label

    return None
