from __future__ import annotations

from datetime import date
from decimal import Decimal

import pytest

from quartermaster import month_service, service
from quartermaster.models import Posting, Section


def _seed_month(db, section=Section.fixed_bill, name="Rent", planned="1200"):
    service.add_entry(db, Section.income, "Paycheck", Decimal("2500"))
    service.add_entry(db, section, name, Decimal(planned))
    return month_service.create_month(db, "2026-04")


def test_add_posting_sums_into_applied(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    month_service.add_posting(
        db, month, rent.id, date(2026, 4, 1), Decimal("600"), description="first half"
    )
    month_service.add_posting(
        db, month, rent.id, date(2026, 4, 15), Decimal("600"), description="second half"
    )
    db.refresh(rent)
    assert rent.applied == Decimal("1200.00")
    assert len(rent.postings) == 2


def test_empty_entry_applied_is_zero(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    assert rent.applied == Decimal("0.00")
    assert rent.postings == []


def test_negative_posting_allowed(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    month_service.add_posting(
        db, month, rent.id, date(2026, 4, 1), Decimal("1200")
    )
    month_service.add_posting(
        db, month, rent.id, date(2026, 4, 3), Decimal("-50"), description="refund"
    )
    db.refresh(rent)
    assert rent.applied == Decimal("1150.00")


def test_update_posting(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    posting = month_service.add_posting(
        db, month, rent.id, date(2026, 4, 1), Decimal("1100")
    )
    month_service.update_posting(
        db, month, posting.id,
        amount=Decimal("1200"),
        description="corrected",
        occurred_on=date(2026, 4, 3),
    )
    db.refresh(rent)
    assert rent.applied == Decimal("1200.00")
    updated = rent.postings[0]
    assert updated.description == "corrected"
    assert updated.occurred_on == date(2026, 4, 3)


def test_delete_posting(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    p1 = month_service.add_posting(db, month, rent.id, date.today(), Decimal("600"))
    p2 = month_service.add_posting(db, month, rent.id, date.today(), Decimal("600"))
    entry = month_service.delete_posting(db, month, p1.id)
    db.refresh(rent)
    assert entry.id == rent.id
    assert rent.applied == Decimal("600.00")
    assert len(rent.postings) == 1
    assert rent.postings[0].id == p2.id


def test_delete_entry_cascades_postings(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    month_service.add_posting(db, month, rent.id, date.today(), Decimal("600"))
    month_service.add_posting(db, month, rent.id, date.today(), Decimal("600"))
    # Postings count before deletion
    all_postings = db.query(Posting).filter(Posting.month_entry_id == rent.id).count()
    assert all_postings == 2
    month_service.delete_month_entry(db, month, rent.id)
    db.expire_all()
    remaining = db.query(Posting).filter(Posting.month_entry_id == rent.id).count()
    assert remaining == 0


def test_postings_ordered_desc(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    month_service.add_posting(db, month, rent.id, date(2026, 4, 1), Decimal("100"))
    month_service.add_posting(db, month, rent.id, date(2026, 4, 15), Decimal("200"))
    month_service.add_posting(db, month, rent.id, date(2026, 4, 8), Decimal("150"))
    db.refresh(rent)
    dates = [p.occurred_on for p in rent.postings]
    assert dates == [date(2026, 4, 15), date(2026, 4, 8), date(2026, 4, 1)]


def test_update_month_entry_rejects_applied_kwarg(db):
    month = _seed_month(db)
    rent = next(e for e in month.entries if e.origin_name == "Rent")
    with pytest.raises(TypeError):
        month_service.update_month_entry(
            db, month, rent.id, applied=Decimal("500")
        )


# ----- Route-level tests -----

def _seed_via_api(client):
    client.post(
        "/sections/income/entries",
        data={"name": "Paycheck", "amount": "2500"},
    )
    client.post(
        "/sections/fixed_bill/entries",
        data={"name": "Rent", "amount": "1200"},
    )
    client.post("/month/2026-04/create")


def test_add_posting_route(client):
    _seed_via_api(client)
    response = client.post(
        "/month/2026-04/entries/2/postings",
        data={
            "occurred_on": "2026-04-01",
            "amount": "1200",
            "description": "April rent",
            "payee": "Landlord",
        },
    )
    assert response.status_code == 200
    assert "April rent" in response.text
    assert "Landlord" in response.text


def test_update_posting_route(client):
    _seed_via_api(client)
    client.post(
        "/month/2026-04/entries/2/postings",
        data={"occurred_on": "2026-04-01", "amount": "1100"},
    )
    # posting id is 1 (first created)
    response = client.post(
        "/month/2026-04/postings/1",
        data={"amount": "1200", "description": "fixed amount"},
    )
    assert response.status_code == 200
    assert "fixed amount" in response.text


def test_delete_posting_route(client):
    _seed_via_api(client)
    client.post(
        "/month/2026-04/entries/2/postings",
        data={"occurred_on": "2026-04-01", "amount": "1200"},
    )
    response = client.delete("/month/2026-04/postings/1")
    assert response.status_code == 200
    # The section partial should render with no postings and applied back to $0
    assert "$0.00" in response.text


def test_posting_routes_reject_invalid_date(client):
    _seed_via_api(client)
    response = client.post(
        "/month/2026-04/entries/2/postings",
        data={"occurred_on": "not-a-date", "amount": "100"},
    )
    assert response.status_code == 400


def test_posting_rejected_on_closed_month(client):
    _seed_via_api(client)
    # Post balancing transactions so the month can close
    client.post(
        "/month/2026-04/entries/1/postings",
        data={"occurred_on": "2026-04-01", "amount": "1200"},
    )
    client.post(
        "/month/2026-04/entries/2/postings",
        data={"occurred_on": "2026-04-01", "amount": "1200"},
    )
    client.post("/month/2026-04/activate")
    close = client.post("/month/2026-04/close")
    assert close.status_code == 204
    # Now try to add a new posting on the closed month
    response = client.post(
        "/month/2026-04/entries/1/postings",
        data={"occurred_on": "2026-04-20", "amount": "1"},
    )
    assert response.status_code == 400


def test_applied_update_via_posting_flips_zero_tone(client):
    _seed_via_api(client)
    response = client.post(
        "/month/2026-04/entries/2/postings",
        data={"occurred_on": "2026-04-01", "amount": "1200"},
    )
    assert response.status_code == 200
    # With only rent applied, applied zero = 0 - 1200 = -1200 -> negative
    assert "tone-negative" in response.text


def test_posting_count_badge_renders(client):
    _seed_via_api(client)
    for i in range(3):
        client.post(
            "/month/2026-04/entries/2/postings",
            data={"occurred_on": "2026-04-01", "amount": "400"},
        )
    page = client.get("/month/2026-04")
    assert page.status_code == 200
    assert "3 txns" in page.text
