"""Dashboard — Stories 8.1–8.4."""
import time
from decimal import Decimal
from datetime import date, timedelta

import pytest

from app.extensions import db as _db
from app.models.account import Account
from app.models.budget import Budget
from app.models.category import Category
from app.models.transaction import Transaction
from app.models.bill import Bill
from app.models.debt import Debt
from app.models.settings import Settings
from app.models.paydown_plan import PaydownPlan
from app.models.paydown_plan_card import PaydownPlanCard
from app.services.amortization import calculate_avalanche


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

def _make_account(name="Checking"):
    a = Account(name=name, type="checking", is_active=True)
    _db.session.add(a)
    _db.session.commit()
    return a


def _make_category(name="Groceries"):
    cat = Category(name=name, is_system=True, is_active=True)
    _db.session.add(cat)
    _db.session.commit()
    return cat


def _make_budget(cat, amount="300.00"):
    today = date.today()
    b = Budget(category_id=cat.id, month=today.month, year=today.year,
               amount=Decimal(amount))
    _db.session.add(b)
    _db.session.commit()
    return b


def _make_transaction(account, cat, amount="50.00", days_ago=2):
    d = (date.today() - timedelta(days=days_ago)).isoformat()
    t = Transaction(
        date=d, merchant_normalized="Shop",
        amount=Decimal(amount), is_credit=False, is_manual=True,
        account_id=account.id, category_id=cat.id,
    )
    _db.session.add(t)
    _db.session.commit()
    return t


def _make_bill(name="Electric", days_until=5, amount="80.00"):
    due = (date.today() + timedelta(days=days_until)).isoformat()
    bill = Bill(name=name, amount=Decimal(amount), due_day=date.today().day,
                due_date=due, is_active=True)
    _db.session.add(bill)
    _db.session.commit()
    return bill


def _make_credit_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"):
    plan = PaydownPlan(strategy="avalanche", 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))
    first_payment = result.per_card_schedule[0]["schedule"][0]["payment"] if result.per_card_schedule else "50"
    plan_card = PaydownPlanCard(
        plan_id=plan.id, account_id=card.id,
        monthly_allocation=Decimal(first_payment),
        starting_balance=card.current_balance,
        starting_apr=card.apr,
    )
    _db.session.add(plan_card)
    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


# ── Story 8.1: Dashboard Shell ────────────────────────────────────────────────

class TestDashboardShell:
    def test_dashboard_returns_200(self, client, db):
        assert client.get("/").status_code == 200

    def test_dashboard_at_root_url(self, client, db):
        response = client.get("/")
        assert response.status_code == 200

    def test_active_page_is_dashboard(self, client, db):
        response = client.get("/")
        assert b"Dashboard" in response.data

    def test_dashboard_renders_in_under_2_seconds(self, client, db):
        # Seed reasonable amount of data
        acct = _make_account()
        cat = _make_category()
        _make_budget(cat)
        for i in range(20):
            _make_transaction(acct, cat, f"{10 + i}.00", days_ago=i)
        _make_bill()
        start = time.time()
        response = client.get("/")
        elapsed = time.time() - start
        assert response.status_code == 200
        assert elapsed < 2.0, f"Dashboard too slow: {elapsed:.2f}s"


# ── Story 8.2: Budget Burn & Monthly Summary Widgets ─────────────────────────

class TestBudgetBurnWidget:
    def test_budget_burn_widget_present(self, client, db):
        response = client.get("/")
        assert b"Budget Burn" in response.data

    def test_budget_burn_shows_category_data(self, client, db):
        acct = _make_account()
        cat = _make_category("Dining")
        _make_budget(cat, "200.00")
        _make_transaction(acct, cat, "50.00")
        response = client.get("/")
        assert b"Dining" in response.data

    def test_budget_burn_empty_state_when_no_budgets(self, client, db):
        response = client.get("/")
        assert b"No budgets set" in response.data or b"Budget Burn" in response.data

    def test_budget_burn_links_to_budgets(self, client, db):
        response = client.get("/")
        assert b"/budgets" in response.data


class TestMonthlySummaryWidget:
    def test_monthly_summary_widget_present(self, client, db):
        response = client.get("/")
        assert b"Monthly Spending" in response.data

    def test_monthly_summary_shows_total(self, client, db):
        acct = _make_account()
        cat = _make_category()
        _make_transaction(acct, cat, "150.00")
        response = client.get("/")
        assert b"150" in response.data

    def test_monthly_summary_empty_state_when_no_transactions(self, client, db):
        response = client.get("/")
        assert b"No transactions" in response.data or b"Monthly Spending" in response.data

    def test_monthly_summary_links_to_transactions(self, client, db):
        response = client.get("/")
        assert b"/transactions" in response.data


# ── Story 8.3: Upcoming Bills & Recent Transactions Widgets ──────────────────

class TestUpcomingBillsWidget:
    def test_upcoming_bills_widget_present(self, client, db):
        response = client.get("/")
        assert b"Upcoming Bills" in response.data

    def test_upcoming_bills_shows_bill_data(self, client, db):
        _make_bill("Netflix", days_until=3, amount="15.99")
        response = client.get("/")
        assert b"Netflix" in response.data

    def test_upcoming_bills_empty_state_when_none(self, client, db):
        response = client.get("/")
        assert b"No bills" in response.data or b"Upcoming Bills" in response.data

    def test_upcoming_bills_links_to_bills(self, client, db):
        response = client.get("/")
        assert b"/bills" in response.data

    def test_overdue_bill_shows_overdue_status(self, client, db):
        bill = Bill(name="Overdue Bill", amount=Decimal("50"), due_day=1,
                    due_date=(date.today() - timedelta(days=2)).isoformat(), is_active=True)
        _db.session.add(bill)
        _db.session.commit()
        response = client.get("/")
        assert b"Overdue" in response.data


class TestRecentTransactionsWidget:
    def test_recent_transactions_widget_present(self, client, db):
        response = client.get("/")
        assert b"Recent Transactions" in response.data

    def test_recent_transactions_shows_last_10(self, client, db):
        acct = _make_account()
        cat = _make_category()
        for i in range(12):
            t = Transaction(
                date=(date.today() - timedelta(days=i)).isoformat(),
                merchant_normalized=f"Merchant{i:02d}",
                amount=Decimal("10.00"), is_credit=False, is_manual=True,
                account_id=acct.id, category_id=cat.id,
            )
            _db.session.add(t)
        _db.session.commit()
        response = client.get("/")
        # First 10 merchants should appear, 11th and 12th should not
        assert b"Merchant00" in response.data
        assert b"Merchant09" in response.data
        assert b"Merchant11" not in response.data

    def test_recent_transactions_empty_state_when_none(self, client, db):
        response = client.get("/")
        assert b"No transactions" in response.data or b"Recent Transactions" in response.data

    def test_recent_transactions_links_to_transactions(self, client, db):
        response = client.get("/")
        assert b"/transactions" in response.data


# ── Story 8.4: Debt Paydown Progress Widget ───────────────────────────────────

class TestDebtPaydownWidget:
    def test_paydown_widget_present(self, client, db):
        response = client.get("/")
        assert b"Debt Paydown" in response.data

    def test_paydown_empty_state_when_no_plan(self, client, db):
        response = client.get("/")
        assert b"No active paydown plan" in response.data or b"Compare strategies" in response.data

    def test_paydown_shows_card_data_when_plan_active(self, client, db):
        card = _make_credit_card("Chase Sapphire")
        _make_active_plan(card)
        response = client.get("/")
        assert b"Chase Sapphire" in response.data

    def test_paydown_shows_strategy_name(self, client, db):
        card = _make_credit_card()
        _make_active_plan(card)
        response = client.get("/")
        assert b"Avalanche" in response.data or b"avalanche" in response.data

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


# ── All widgets present in all states ────────────────────────────────────────

class TestAllWidgetsPresentAllStates:
    def test_all_5_widget_headings_render_empty(self, client, db):
        response = client.get("/")
        for heading in [b"Budget Burn", b"Monthly Spending",
                        b"Upcoming Bills", b"Recent Transactions", b"Debt Paydown"]:
            assert heading in response.data

    def test_all_5_widget_headings_render_with_data(self, client, db):
        acct = _make_account()
        cat = _make_category()
        _make_budget(cat)
        _make_transaction(acct, cat, "75.00")
        _make_bill("Electric", days_until=7)
        card = _make_credit_card()
        _make_active_plan(card)
        response = client.get("/")
        assert response.status_code == 200
        for heading in [b"Budget Burn", b"Monthly Spending",
                        b"Upcoming Bills", b"Recent Transactions", b"Debt Paydown"]:
            assert heading in response.data
