"""Historical budget vs. actuals — Story 4.5."""
from decimal import Decimal
from datetime import date

import pytest

from app.extensions import db as _db
from app.models.budget import Budget
from app.models.category import Category
from app.models.account import Account
from app.models.transaction import Transaction


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, year, month, amount):
    b = Budget(category_id=cat.id, year=year, month=month, amount=Decimal(str(amount)))
    _db.session.add(b)
    _db.session.commit()
    return b


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


def _make_transaction(acct, cat, year, month, amount):
    txn = Transaction(
        date=f"{year}-{month:02d}-15",
        merchant_normalized="Shop",
        amount=Decimal(str(amount)),
        is_credit=False,
        account_id=acct.id,
        category_id=cat.id,
    )
    _db.session.add(txn)
    _db.session.commit()
    return txn


class TestMonthPicker:
    def test_default_is_current_month(self, client, db):
        today = date.today()
        response = client.get("/budgets/")
        assert response.status_code == 200
        assert today.strftime("%Y-%m").encode() in response.data

    def test_picker_shows_month_with_budget_data(self, client, db):
        cat = _make_category()
        _make_budget(cat, 2025, 3, "200.00")
        response = client.get("/budgets/")
        assert b"2025" in response.data
        assert b"March" in response.data

    def test_picker_shows_month_with_transaction_data(self, client, db):
        cat = _make_category()
        acct = _make_account()
        _make_transaction(acct, cat, 2025, 6, "50.00")
        response = client.get("/budgets/")
        assert b"2025" in response.data
        assert b"June" in response.data

    def test_picker_includes_current_month_always(self, client, db):
        today = date.today()
        response = client.get("/budgets/")
        assert today.strftime("%Y").encode() in response.data


class TestHistoricalView:
    def test_month_param_selects_past_month(self, client, db):
        cat = _make_category("Housing")
        _make_budget(cat, 2025, 4, "1200.00")
        response = client.get("/budgets/?month=2025-04")
        assert response.status_code == 200
        assert b"1200" in response.data

    def test_past_month_shows_read_only_notice(self, client, db):
        _make_category()
        response = client.get("/budgets/?month=2020-01")
        assert b"read-only" in response.data.lower() or b"historical" in response.data.lower()

    def test_past_month_shows_correct_month_name(self, client, db):
        _make_category()
        response = client.get("/budgets/?month=2024-11")
        assert b"November" in response.data
        assert b"2024" in response.data

    def test_current_month_link_shown_for_past_month(self, client, db):
        _make_category()
        response = client.get("/budgets/?month=2020-01")
        assert b"Current month" in response.data or b"current month" in response.data

    def test_current_month_link_hidden_for_current_month(self, client, db):
        today = date.today()
        response = client.get(f"/budgets/?month={today.strftime('%Y-%m')}")
        assert b"Current month" not in response.data

    def test_invalid_month_param_falls_back_to_current(self, client, db):
        today = date.today()
        response = client.get("/budgets/?month=not-a-date")
        assert response.status_code == 200
        assert today.strftime("%Y").encode() in response.data

    def test_url_is_bookmarkable(self, client, db):
        cat = _make_category()
        _make_budget(cat, 2025, 2, "500.00")
        response = client.get("/budgets/?month=2025-02")
        assert response.status_code == 200
        assert b"2025-02" in response.data


class TestHistoricalReadOnly:
    def test_post_to_past_month_redirects_with_error(self, client, db):
        cat = _make_category()
        response = client.post(
            "/budgets/?month=2020-01",
            data={"category_id": cat.id, "amount": "100.00"},
            follow_redirects=True,
        )
        assert b"current month" in response.data.lower() or b"read-only" in response.data.lower() or b"only" in response.data.lower()

    def test_post_to_past_month_does_not_save(self, client, db):
        cat = _make_category()
        client.post(
            "/budgets/?month=2020-01",
            data={"category_id": cat.id, "amount": "100.00"},
        )
        assert Budget.query.filter_by(category_id=cat.id, year=2020, month=1).count() == 0


class TestHistoricalActuals:
    def test_actuals_show_for_selected_month(self, client, db):
        cat = _make_category("Dining")
        acct = _make_account()
        _make_budget(cat, 2025, 5, "300.00")
        _make_transaction(acct, cat, 2025, 5, "75.00")
        response = client.get("/budgets/?month=2025-05")
        assert b"75" in response.data
        assert b"300" in response.data

    def test_actuals_do_not_bleed_across_months(self, client, db):
        cat = _make_category("Dining")
        acct = _make_account()
        _make_budget(cat, 2025, 5, "300.00")
        _make_transaction(acct, cat, 2025, 6, "999.00")
        response = client.get("/budgets/?month=2025-05")
        assert b"999" not in response.data


class TestBudgetsApiMonthParam:
    def test_api_returns_current_month_by_default(self, client, db):
        cat = _make_category("Groceries")
        today = date.today()
        _make_budget(cat, today.year, today.month, "200.00")
        response = client.get("/api/budgets")
        assert response.status_code == 200
        data = response.get_json()
        groceries = next((d for d in data if d["category"] == "Groceries"), None)
        assert groceries is not None
        assert groceries["budgeted"] == 200.0

    def test_api_accepts_month_param(self, client, db):
        cat = _make_category("Housing")
        _make_budget(cat, 2025, 3, "1500.00")
        response = client.get("/api/budgets?month=2025-03")
        assert response.status_code == 200
        data = response.get_json()
        housing = next((d for d in data if d["category"] == "Housing"), None)
        assert housing is not None
        assert housing["budgeted"] == 1500.0

    def test_api_month_param_does_not_leak_other_months(self, client, db):
        cat = _make_category("Housing")
        _make_budget(cat, 2025, 3, "1500.00")
        _make_budget(cat, 2025, 4, "2000.00")
        response = client.get("/api/budgets?month=2025-03")
        data = response.get_json()
        housing = next((d for d in data if d["category"] == "Housing"), None)
        assert housing["budgeted"] == 1500.0

    def test_api_invalid_month_falls_back_to_current(self, client, db):
        response = client.get("/api/budgets?month=bad-value")
        assert response.status_code == 200

    def test_api_includes_actuals_for_month(self, client, db):
        cat = _make_category("Groceries")
        acct = _make_account()
        _make_budget(cat, 2025, 7, "400.00")
        _make_transaction(acct, cat, 2025, 7, "120.00")
        response = client.get("/api/budgets?month=2025-07")
        data = response.get_json()
        groceries = next((d for d in data if d["category"] == "Groceries"), None)
        assert groceries["spent"] == pytest.approx(120.0)
