"""Paydown Planner — Stories 6.1, 6.3, 6.4, 6.5."""
from decimal import Decimal
import json

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


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

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


def _set_extra(amount="200.00"):
    settings = Settings.query.first()
    if settings is None:
        settings = Settings(id=1, extra_monthly_payment=Decimal(amount))
        _db.session.add(settings)
    else:
        settings.extra_monthly_payment = Decimal(amount)
    _db.session.commit()
    return settings


# ── Story 6.1: Credit Card Account Setup ─────────────────────────────────────

class TestCreditCardCreate:
    def test_get_create_form_returns_200(self, client, db):
        assert client.get("/paydown/cards/create").status_code == 200

    def test_valid_post_creates_card(self, client, db):
        client.post("/paydown/cards/create", data={
            "name": "Chase Freedom",
            "current_balance": "1500.00",
            "apr": "19.99",
            "min_payment": "35.00",
            "credit_limit": "5000.00",
        })
        card = Account.query.filter_by(name="Chase Freedom").first()
        assert card is not None
        assert card.type == "credit"
        assert card.apr == Decimal("19.99")

    def test_valid_post_redirects(self, client, db):
        response = client.post("/paydown/cards/create", data={
            "name": "Test Card",
            "current_balance": "500.00",
            "apr": "15.00",
            "min_payment": "15.00",
        }, follow_redirects=False)
        assert response.status_code == 302

    def test_valid_post_flashes_success(self, client, db):
        response = client.post("/paydown/cards/create", data={
            "name": "Discover",
            "current_balance": "800.00",
            "apr": "22.00",
            "min_payment": "20.00",
        }, follow_redirects=True)
        assert b"added" in response.data.lower()

    def test_invalid_balance_rejected(self, client, db):
        client.post("/paydown/cards/create", data={
            "name": "Bad",
            "current_balance": "not-a-number",
            "apr": "20.00",
            "min_payment": "25.00",
        })
        assert Account.query.filter_by(type="credit").count() == 0

    def test_credit_limit_optional(self, client, db):
        client.post("/paydown/cards/create", data={
            "name": "No Limit Card",
            "current_balance": "500.00",
            "apr": "18.00",
            "min_payment": "15.00",
            "credit_limit": "",
        })
        card = Account.query.filter_by(name="No Limit Card").first()
        assert card is not None
        assert card.credit_limit is None


class TestCreditCardEdit:
    def test_get_edit_returns_200(self, client, db):
        card = _make_card()
        assert client.get(f"/paydown/cards/{card.id}/edit").status_code == 200

    def test_edit_updates_card(self, client, db):
        card = _make_card()
        client.post(f"/paydown/cards/{card.id}/edit", data={
            "name": "Updated Card",
            "current_balance": "2000.00",
            "apr": "15.00",
            "min_payment": "50.00",
        })
        _db.session.refresh(card)
        assert card.name == "Updated Card"
        assert card.current_balance == Decimal("2000.00")

    def test_edit_nonexistent_returns_404(self, client, db):
        assert client.get("/paydown/cards/9999/edit").status_code == 404


# ── Story 6.3: Strategy Comparison View ───────────────────────────────────────

class TestStrategyCompare:
    def test_redirects_to_extra_payment_if_not_set(self, client, db):
        _make_card()
        response = client.get("/paydown/compare", follow_redirects=False)
        assert response.status_code == 302
        assert "extra" in response.headers["Location"]

    def test_shows_empty_state_when_no_debt(self, client, db):
        _set_extra()
        # Card with $0 balance
        card = Account(name="Paid Off", type="credit", is_active=True,
                       current_balance=Decimal("0"), apr=Decimal("20"),
                       min_payment=Decimal("25"))
        _db.session.add(card)
        _db.session.commit()
        response = client.get("/paydown/compare")
        assert b"No outstanding" in response.data or b"0" in response.data

    def test_shows_all_4_strategies(self, client, db):
        _make_card()
        _set_extra()
        response = client.get("/paydown/compare")
        assert response.status_code == 200
        for label in [b"Avalanche", b"Snowball", b"Highest Balance", b"Proportional"]:
            assert label in response.data

    def test_shows_months_to_payoff(self, client, db):
        _make_card()
        _set_extra()
        response = client.get("/paydown/compare")
        assert response.status_code == 200

    def test_compare_returns_200(self, client, db):
        _make_card()
        _set_extra()
        assert client.get("/paydown/compare").status_code == 200


# ── Story 6.4: Strategy Detail ────────────────────────────────────────────────

class TestStrategyDetail:
    def test_avalanche_detail_returns_200(self, client, db):
        _make_card()
        _set_extra()
        assert client.get("/paydown/strategy/avalanche").status_code == 200

    def test_snowball_detail_returns_200(self, client, db):
        _make_card()
        _set_extra()
        assert client.get("/paydown/strategy/snowball").status_code == 200

    def test_highest_balance_detail_returns_200(self, client, db):
        _make_card()
        _set_extra()
        assert client.get("/paydown/strategy/highest_balance").status_code == 200

    def test_proportional_detail_returns_200(self, client, db):
        _make_card()
        _set_extra()
        assert client.get("/paydown/strategy/proportional").status_code == 200

    def test_invalid_strategy_returns_404(self, client, db):
        assert client.get("/paydown/strategy/nonexistent").status_code == 404

    def test_detail_shows_months_and_interest(self, client, db):
        _make_card()
        _set_extra()
        response = client.get("/paydown/strategy/avalanche")
        assert b"Months to Payoff" in response.data
        assert b"Total Interest" in response.data

    def test_detail_shows_activate_button(self, client, db):
        _make_card()
        _set_extra()
        response = client.get("/paydown/strategy/avalanche")
        assert b"Activate" in response.data

    def test_api_schedule_returns_json(self, client, db):
        _make_card()
        _set_extra()
        response = client.get("/api/paydown/schedule/avalanche")
        assert response.status_code == 200
        data = response.get_json()
        assert isinstance(data, list)

    def test_api_schedule_has_card_data(self, client, db):
        _make_card("Visa")
        _set_extra()
        response = client.get("/api/paydown/schedule/avalanche")
        data = response.get_json()
        assert len(data) == 1
        assert "account_name" in data[0]
        assert "schedule" in data[0]

    def test_api_invalid_strategy_returns_404(self, client, db):
        assert client.get("/api/paydown/schedule/invalid").status_code == 404


# ── Story 6.5: Plan Activation ────────────────────────────────────────────────

class TestPlanActivation:
    def test_activate_creates_paydown_plan(self, client, db):
        _make_card()
        _set_extra()
        client.post("/paydown/activate/avalanche")
        plan = PaydownPlan.query.filter_by(status="active").first()
        assert plan is not None
        assert plan.strategy == "avalanche"

    def test_activate_redirects_to_monitor(self, client, db):
        _make_card()
        _set_extra()
        response = client.post("/paydown/activate/avalanche", follow_redirects=False)
        assert response.status_code == 302
        assert "monitor" in response.headers["Location"]

    def test_activate_flashes_success(self, client, db):
        _make_card()
        _set_extra()
        response = client.post("/paydown/activate/avalanche", follow_redirects=True)
        assert b"activated" in response.data.lower()

    def test_activate_archives_old_plan(self, client, db):
        _make_card()
        _set_extra()
        # Activate once
        client.post("/paydown/activate/avalanche")
        assert PaydownPlan.query.filter_by(status="active").count() == 1
        # Activate a different strategy
        client.post("/paydown/activate/snowball")
        assert PaydownPlan.query.filter_by(status="active").count() == 1
        assert PaydownPlan.query.filter_by(status="archived").count() == 1

    def test_only_one_plan_active_at_a_time(self, client, db):
        _make_card()
        _set_extra()
        client.post("/paydown/activate/avalanche")
        client.post("/paydown/activate/snowball")
        client.post("/paydown/activate/proportional")
        assert PaydownPlan.query.filter_by(status="active").count() == 1

    def test_activate_creates_plan_cards(self, client, db):
        _make_card()
        _set_extra()
        client.post("/paydown/activate/avalanche")
        plan = PaydownPlan.query.filter_by(status="active").first()
        assert plan is not None
        assert plan.cards.count() == 1

    def test_archived_plan_has_archived_at_set(self, client, db):
        _make_card()
        _set_extra()
        client.post("/paydown/activate/avalanche")
        client.post("/paydown/activate/snowball")
        archived = PaydownPlan.query.filter_by(status="archived").first()
        assert archived.archived_at is not None

    def test_invalid_strategy_returns_404(self, client, db):
        _make_card()
        _set_extra()
        assert client.post("/paydown/activate/invalid").status_code == 404

    def test_monitor_shows_active_plan(self, client, db):
        _make_card()
        _set_extra()
        client.post("/paydown/activate/avalanche")
        response = client.get("/paydown/monitor")
        assert response.status_code == 200
        assert b"avalanche" in response.data.lower() or b"Avalanche" in response.data


# ── Extra Payment Settings ────────────────────────────────────────────────────

class TestExtraPayment:
    def test_get_extra_form_returns_200(self, client, db):
        assert client.get("/paydown/extra-payment").status_code == 200

    def test_valid_post_saves_and_redirects_to_compare(self, client, db):
        _make_card()
        response = client.post("/paydown/extra-payment",
                               data={"extra_monthly_payment": "150.00"},
                               follow_redirects=False)
        assert response.status_code == 302
        assert "compare" in response.headers["Location"]

    def test_invalid_amount_rerenders(self, client, db):
        response = client.post("/paydown/extra-payment",
                               data={"extra_monthly_payment": "not-a-number"})
        assert response.status_code == 200

    def test_saves_to_settings(self, client, db):
        client.post("/paydown/extra-payment",
                    data={"extra_monthly_payment": "250.00"})
        settings = Settings.query.first()
        assert settings is not None
        assert settings.extra_monthly_payment == Decimal("250.00")
