"""Budget entry per category — Story 4.2."""
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


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


# ── GET ───────────────────────────────────────────────────────────────────────

class TestBudgetsGet:
    def test_returns_200(self, client, db):
        assert client.get("/budgets/").status_code == 200

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

    def test_inactive_category_not_shown(self, client, db):
        cat = Category(name="OldCat", is_system=False, is_active=False)
        _db.session.add(cat)
        _db.session.commit()
        response = client.get("/budgets/")
        assert b"OldCat" not in response.data

    def test_shows_current_budget_amount(self, client, db):
        cat = _make_category("Housing")
        today = date.today()
        b = Budget(
            category_id=cat.id,
            month=today.month,
            year=today.year,
            amount=Decimal("1500.00"),
        )
        _db.session.add(b)
        _db.session.commit()
        response = client.get("/budgets/")
        assert b"1500" in response.data

    def test_category_with_no_budget_renders_without_error(self, client, db):
        _make_category("Dining")
        response = client.get("/budgets/")
        assert response.status_code == 200

    def test_active_page_is_budgets(self, client, db):
        response = client.get("/budgets/")
        assert b"aria-current" in response.data or b"budgets" in response.data


# ── POST valid ────────────────────────────────────────────────────────────────

class TestBudgetsPostValid:
    def test_valid_post_redirects(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/",
            data={"category_id": cat.id, "amount": "300.00"},
            follow_redirects=False,
        )
        assert response.status_code == 302

    def test_redirect_target_is_budgets_page(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/",
            data={"category_id": cat.id, "amount": "300.00"},
            follow_redirects=False,
        )
        assert "/budgets" in response.headers["Location"]

    def test_valid_post_saves_budget(self, client, db):
        cat = _make_category("Groceries")
        today = date.today()
        client.post("/budgets/", data={"category_id": cat.id, "amount": "300.00"})
        b = Budget.query.filter_by(
            category_id=cat.id, month=today.month, year=today.year
        ).first()
        assert b is not None
        assert b.amount == Decimal("300.00")

    def test_valid_post_flashes_success(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/",
            data={"category_id": cat.id, "amount": "300.00"},
            follow_redirects=True,
        )
        assert b"saved" in response.data.lower() or b"success" in response.data.lower()

    def test_upsert_updates_existing_budget(self, client, db):
        cat = _make_category("Groceries")
        today = date.today()
        b = Budget(
            category_id=cat.id,
            month=today.month,
            year=today.year,
            amount=Decimal("200.00"),
        )
        _db.session.add(b)
        _db.session.commit()
        client.post("/budgets/", data={"category_id": cat.id, "amount": "350.00"})
        _db.session.refresh(b)
        assert b.amount == Decimal("350.00")
        assert Budget.query.filter_by(category_id=cat.id).count() == 1

    def test_budget_is_month_specific(self, client, db):
        cat = _make_category("Groceries")
        today = date.today()
        other_month = 1 if today.month != 1 else 2
        existing = Budget(
            category_id=cat.id,
            month=other_month,
            year=today.year,
            amount=Decimal("100.00"),
        )
        _db.session.add(existing)
        _db.session.commit()
        client.post("/budgets/", data={"category_id": cat.id, "amount": "300.00"})
        assert Budget.query.filter_by(category_id=cat.id).count() == 2

    def test_zero_amount_accepted(self, client, db):
        cat = _make_category("Dining")
        response = client.post(
            "/budgets/",
            data={"category_id": cat.id, "amount": "0"},
            follow_redirects=False,
        )
        assert response.status_code == 302

    def test_decimal_precision_stored(self, client, db):
        cat = _make_category("Groceries")
        today = date.today()
        client.post("/budgets/", data={"category_id": cat.id, "amount": "123.45"})
        b = Budget.query.filter_by(
            category_id=cat.id, month=today.month, year=today.year
        ).first()
        assert b.amount == Decimal("123.45")


# ── POST invalid ──────────────────────────────────────────────────────────────

class TestBudgetsPostInvalid:
    def test_non_numeric_rerenders_form(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/", data={"category_id": cat.id, "amount": "not-a-number"}
        )
        assert response.status_code == 200

    def test_non_numeric_shows_error(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/", data={"category_id": cat.id, "amount": "abc"}
        )
        data = response.data.lower()
        assert b"valid" in data or b"numeric" in data or b"number" in data

    def test_negative_amount_rejected(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/", data={"category_id": cat.id, "amount": "-100"}
        )
        assert response.status_code == 200

    def test_negative_amount_shows_error(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/", data={"category_id": cat.id, "amount": "-100"}
        )
        data = response.data.lower()
        assert (
            b"zero or greater" in data
            or b"negative" in data
            or b"positive" in data
            or b"must be" in data
        )

    def test_negative_does_not_save(self, client, db):
        cat = _make_category("Groceries")
        client.post("/budgets/", data={"category_id": cat.id, "amount": "-50"})
        assert Budget.query.count() == 0

    def test_empty_amount_rerenders(self, client, db):
        cat = _make_category("Groceries")
        response = client.post(
            "/budgets/", data={"category_id": cat.id, "amount": ""}
        )
        assert response.status_code == 200

    def test_invalid_category_returns_404(self, client, db):
        response = client.post(
            "/budgets/", data={"category_id": 9999, "amount": "100"}
        )
        assert response.status_code == 404
