"""Paydown Monitor — Stories 7.1, 7.2, 7.3."""
from decimal import Decimal
import datetime

import pytest

from app.extensions import db as _db
from app.models.account import Account
from app.models.settings import Settings
from app.models.paydown_plan import PaydownPlan
from app.models.paydown_plan_card import PaydownPlanCard
from app.models.paydown_balance_update import PaydownBalanceUpdate
from app.services.amortization import calculate_avalanche


# ── fixtures ─────────────────────────────────────────────────────────────────

def _make_card(name="Visa", balance="2000.00", apr="20.00", min_pay="50.00"):
    card = Account(
        name=name, type="credit", is_active=True,
        current_balance=Decimal(balance),
        apr=Decimal(apr),
        min_payment=Decimal(min_pay),
    )
    _db.session.add(card)
    _db.session.commit()
    return card


def _make_active_plan(card, extra="200.00", strategy="avalanche"):
    plan = PaydownPlan(strategy=strategy, extra_monthly=Decimal(extra), status="active")
    _db.session.add(plan)
    _db.session.flush()

    card_dict = {
        "id": card.id, "name": card.name,
        "balance": card.current_balance, "apr": card.apr,
        "min_payment": card.min_payment,
    }
    result = calculate_avalanche([card_dict], Decimal(extra))

    plan_card = PaydownPlanCard(
        plan_id=plan.id,
        account_id=card.id,
        monthly_allocation=Decimal(schedule_row["payment"] if (schedule_row := result.per_card_schedule[0]["schedule"][0] if result.per_card_schedule and result.per_card_schedule[0]["schedule"] else None) else "50"),
        starting_balance=card.current_balance,
        starting_apr=card.apr,
    )
    _db.session.add(plan_card)
    _db.session.commit()

    settings = Settings.query.first()
    if settings is None:
        settings = Settings(id=1, extra_monthly_payment=Decimal(extra))
        _db.session.add(settings)
    else:
        settings.extra_monthly_payment = Decimal(extra)
    _db.session.commit()

    return plan


def _make_balance_update(account_id, balance, days_ago=0):
    upd_date = datetime.datetime.now() - datetime.timedelta(days=days_ago)
    upd = PaydownBalanceUpdate(
        account_id=account_id,
        balance=Decimal(str(balance)),
        updated_at=upd_date,
    )
    _db.session.add(upd)
    _db.session.commit()
    return upd


# ── Story 7.1: Monitor View ───────────────────────────────────────────────────

class TestMonitorView:
    def test_monitor_returns_200(self, client, db):
        assert client.get("/paydown/monitor").status_code == 200

    def test_monitor_shows_empty_state_when_no_plan(self, client, db):
        response = client.get("/paydown/monitor")
        assert b"No active plan" in response.data

    def test_monitor_shows_active_plan_info(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.get("/paydown/monitor")
        assert b"avalanche" in response.data.lower() or b"Avalanche" in response.data

    def test_monitor_shows_card_name(self, client, db):
        card = _make_card("Chase Freedom")
        _make_active_plan(card)
        response = client.get("/paydown/monitor")
        assert b"Chase Freedom" in response.data

    def test_monitor_shows_planned_balance(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.get("/paydown/monitor")
        assert b"Planned" in response.data

    def test_monitor_shows_stale_indicator_when_no_updates(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.get("/paydown/monitor")
        assert b"Stale" in response.data or b"stale" in response.data

    def test_monitor_shows_behind_alert_when_over_5pct(self, client, db):
        card = _make_card(balance="2000.00")
        plan = _make_active_plan(card)
        # Very high balance update (clearly behind)
        _make_balance_update(card.id, "2500.00")
        response = client.get("/paydown/monitor")
        assert b"Behind Plan" in response.data or b"behind" in response.data.lower()

    def test_monitor_active_page_is_paydown(self, client, db):
        response = client.get("/paydown/monitor")
        assert b"paydown" in response.data


# ── Story 7.2: Balance Updates ────────────────────────────────────────────────

class TestBalanceUpdates:
    def test_update_creates_balance_update_record(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        client.post("/paydown/monitor/update-balance", data={
            "account_id": card.id,
            "balance": "1800.00",
        })
        upd = PaydownBalanceUpdate.query.filter_by(account_id=card.id).first()
        assert upd is not None
        assert upd.balance == Decimal("1800.00")

    def test_update_redirects_to_monitor(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.post("/paydown/monitor/update-balance", data={
            "account_id": card.id,
            "balance": "1500.00",
        }, follow_redirects=False)
        assert response.status_code == 302
        assert "monitor" in response.headers["Location"]

    def test_update_flashes_success(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.post("/paydown/monitor/update-balance", data={
            "account_id": card.id,
            "balance": "1700.00",
        }, follow_redirects=True)
        assert b"updated" in response.data.lower()

    def test_old_updates_preserved(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        client.post("/paydown/monitor/update-balance",
                    data={"account_id": card.id, "balance": "1900.00"})
        client.post("/paydown/monitor/update-balance",
                    data={"account_id": card.id, "balance": "1700.00"})
        assert PaydownBalanceUpdate.query.filter_by(account_id=card.id).count() == 2

    def test_card_not_in_plan_returns_error(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        other_card = Account(name="Other", type="credit", is_active=True,
                             current_balance=Decimal("500"), apr=Decimal("15"),
                             min_payment=Decimal("15"))
        _db.session.add(other_card)
        _db.session.commit()
        response = client.post("/paydown/monitor/update-balance", data={
            "account_id": other_card.id,
            "balance": "400.00",
        }, follow_redirects=True)
        assert b"not in active plan" in response.data.lower()

    def test_negative_balance_rejected(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.post("/paydown/monitor/update-balance", data={
            "account_id": card.id,
            "balance": "-100.00",
        }, follow_redirects=True)
        assert PaydownBalanceUpdate.query.count() == 0

    def test_invalid_balance_rejected(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.post("/paydown/monitor/update-balance", data={
            "account_id": card.id,
            "balance": "not-a-number",
        }, follow_redirects=True)
        assert PaydownBalanceUpdate.query.count() == 0


# ── Story 7.3: Strategy Switch ────────────────────────────────────────────────

class TestStrategySwitchAndHistory:
    def test_switch_archives_old_plan(self, client, db):
        card = _make_card()
        _make_active_plan(card, strategy="avalanche")
        client.post("/paydown/switch-strategy", data={"strategy": "snowball"})
        archived = PaydownPlan.query.filter_by(status="archived").count()
        assert archived == 1

    def test_switch_creates_new_active_plan(self, client, db):
        card = _make_card()
        _make_active_plan(card, strategy="avalanche")
        client.post("/paydown/switch-strategy", data={"strategy": "snowball"})
        active = PaydownPlan.query.filter_by(status="active").first()
        assert active is not None
        assert active.strategy == "snowball"

    def test_switch_uses_latest_balance_update(self, client, db):
        card = _make_card(balance="2000.00")
        _make_active_plan(card, strategy="avalanche")
        _make_balance_update(card.id, "1500.00")
        client.post("/paydown/switch-strategy", data={"strategy": "snowball"})
        new_plan = PaydownPlan.query.filter_by(status="active").first()
        plan_card = PaydownPlanCard.query.filter_by(plan_id=new_plan.id).first()
        assert plan_card.starting_balance == Decimal("1500.00")

    def test_switch_redirects_to_monitor(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.post("/paydown/switch-strategy",
                               data={"strategy": "snowball"},
                               follow_redirects=False)
        assert response.status_code == 302
        assert "monitor" in response.headers["Location"]

    def test_switch_flashes_success(self, client, db):
        card = _make_card()
        _make_active_plan(card)
        response = client.post("/paydown/switch-strategy",
                               data={"strategy": "snowball"},
                               follow_redirects=True)
        assert b"switched" in response.data.lower()

    def test_archived_plan_has_archived_at(self, client, db):
        card = _make_card()
        _make_active_plan(card, strategy="avalanche")
        client.post("/paydown/switch-strategy", data={"strategy": "snowball"})
        archived = PaydownPlan.query.filter_by(status="archived").first()
        assert archived.archived_at is not None

    def test_history_page_returns_200(self, client, db):
        assert client.get("/paydown/history").status_code == 200

    def test_history_shows_archived_plans(self, client, db):
        card = _make_card()
        _make_active_plan(card, strategy="avalanche")
        client.post("/paydown/switch-strategy", data={"strategy": "snowball"})
        response = client.get("/paydown/history")
        assert b"avalanche" in response.data.lower() or b"Avalanche" in response.data

    def test_history_empty_state_when_no_archived(self, client, db):
        response = client.get("/paydown/history")
        assert b"No archived" in response.data

    def test_strategy_switch_baseline_reset(self, client, db):
        """After switch, new plan uses updated balances, not original starting balances."""
        card = _make_card(balance="2000.00")
        _make_active_plan(card, strategy="avalanche")
        _make_balance_update(card.id, "1200.00")  # significantly paid down
        client.post("/paydown/switch-strategy", data={"strategy": "proportional"})
        new_plan = PaydownPlan.query.filter_by(status="active").first()
        plan_card = PaydownPlanCard.query.filter_by(plan_id=new_plan.id).first()
        # New plan should start from 1200, not 2000
        assert plan_card.starting_balance < Decimal("2000.00")
