"""50/30/20 budget recommendation page — Story 4.3."""
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.settings import Settings


def _set_income(amount='5000.00'):
    s = Settings(id=1, monthly_income=Decimal(amount))
    _db.session.add(s)
    _db.session.commit()
    return s


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


def _seed_needs(amount_each='500.00'):
    """Create a Housing category + set income so the form has a usable input."""
    _set_income('5000')
    housing = _make_category('Housing')
    return housing


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

class TestRecommendGet:
    def test_returns_200_with_income(self, client, db):
        _set_income()
        assert client.get('/budgets/recommend').status_code == 200

    def test_returns_200_without_income(self, client, db):
        assert client.get('/budgets/recommend').status_code == 200

    def test_no_income_shows_settings_link(self, client, db):
        response = client.get('/budgets/recommend')
        assert b'/settings/income' in response.data

    def test_no_income_hides_apply_button(self, client, db):
        response = client.get('/budgets/recommend')
        assert b'Apply' not in response.data

    def test_with_income_shows_apply_button(self, client, db):
        _set_income()
        response = client.get('/budgets/recommend')
        assert b'Apply' in response.data

    def test_shows_matched_category_names(self, client, db):
        _set_income()
        _make_category('Housing')
        response = client.get('/budgets/recommend')
        assert b'Housing' in response.data

    def test_shows_suggested_amounts(self, client, db):
        _set_income('6000')
        _make_category('Housing')
        # With $6000 income, needs_per_cat = 6000 * 0.5 / 6 = $500
        response = client.get('/budgets/recommend')
        assert b'500' in response.data

    def test_shows_savings_info(self, client, db):
        _set_income('1000')
        response = client.get('/budgets/recommend')
        # Savings = $200, shown informational
        assert b'200' in response.data or b'savings' in response.data.lower()

    def test_active_page_is_budgets(self, client, db):
        _set_income()
        response = client.get('/budgets/recommend')
        # base.html marks active nav link for budgets
        assert b'budgets' in response.data


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

class TestRecommendPostValid:
    def test_valid_post_redirects(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        response = client.post(
            '/budgets/recommend',
            data={f'amount_{housing.id}': '500'},
            follow_redirects=False,
        )
        assert response.status_code == 302

    def test_redirect_target_is_budgets(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        response = client.post(
            '/budgets/recommend',
            data={f'amount_{housing.id}': '500'},
            follow_redirects=False,
        )
        assert '/budgets' in response.headers['Location']

    def test_valid_post_flashes_success(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        response = client.post(
            '/budgets/recommend',
            data={f'amount_{housing.id}': '500'},
            follow_redirects=True,
        )
        low = response.data.lower()
        assert b'applied' in low or b'saved' in low or b'success' in low

    def test_saves_budget_record(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        today = date.today()
        client.post('/budgets/recommend', data={f'amount_{housing.id}': '500'})
        b = Budget.query.filter_by(
            category_id=housing.id, month=today.month, year=today.year
        ).first()
        assert b is not None
        assert b.amount == Decimal('500.00')

    def test_saves_multiple_categories(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        dining = _make_category('Dining')
        today = date.today()
        client.post(
            '/budgets/recommend',
            data={f'amount_{housing.id}': '400', f'amount_{dining.id}': '200'},
        )
        assert Budget.query.filter_by(month=today.month, year=today.year).count() == 2

    def test_blank_amounts_skipped_no_record(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        client.post('/budgets/recommend', data={f'amount_{housing.id}': ''})
        assert Budget.query.count() == 0

    def test_zero_amounts_skipped_no_record(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        client.post('/budgets/recommend', data={f'amount_{housing.id}': '0'})
        assert Budget.query.count() == 0

    def test_upserts_existing_budget(self, client, db):
        _set_income('5000')
        housing = _make_category('Housing')
        today = date.today()
        existing = Budget(
            category_id=housing.id, month=today.month, year=today.year,
            amount=Decimal('300.00')
        )
        _db.session.add(existing)
        _db.session.commit()
        client.post('/budgets/recommend', data={f'amount_{housing.id}': '600'})
        _db.session.refresh(existing)
        assert existing.amount == Decimal('600.00')
        assert Budget.query.filter_by(category_id=housing.id).count() == 1

    def test_no_form_inputs_still_redirects(self, client, db):
        _set_income('5000')
        response = client.post('/budgets/recommend', data={}, follow_redirects=False)
        assert response.status_code == 302


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

class TestRecommendPostInvalid:
    def test_total_exceeds_income_rerenders(self, client, db):
        _set_income('100')
        h = _make_category('Housing')
        d = _make_category('Dining')
        response = client.post(
            '/budgets/recommend',
            data={f'amount_{h.id}': '300', f'amount_{d.id}': '300'},
        )
        assert response.status_code == 200

    def test_total_exceeds_income_shows_error(self, client, db):
        _set_income('100')
        h = _make_category('Housing')
        response = client.post('/budgets/recommend', data={f'amount_{h.id}': '500'})
        assert b'exceed' in response.data.lower() or b'income' in response.data.lower()

    def test_total_exceeds_income_does_not_save(self, client, db):
        _set_income('100')
        h = _make_category('Housing')
        client.post('/budgets/recommend', data={f'amount_{h.id}': '500'})
        assert Budget.query.count() == 0

    def test_non_numeric_amount_rerenders(self, client, db):
        _set_income('5000')
        h = _make_category('Housing')
        response = client.post('/budgets/recommend', data={f'amount_{h.id}': 'bad'})
        assert response.status_code == 200

    def test_non_numeric_shows_error(self, client, db):
        _set_income('5000')
        h = _make_category('Housing')
        response = client.post('/budgets/recommend', data={f'amount_{h.id}': 'bad'})
        low = response.data.lower()
        assert b'valid' in low or b'numeric' in low or b'number' in low

    def test_non_numeric_does_not_save(self, client, db):
        _set_income('5000')
        h = _make_category('Housing')
        client.post('/budgets/recommend', data={f'amount_{h.id}': 'bad'})
        assert Budget.query.count() == 0

    def test_negative_amount_rerenders(self, client, db):
        _set_income('5000')
        h = _make_category('Housing')
        response = client.post('/budgets/recommend', data={f'amount_{h.id}': '-100'})
        assert response.status_code == 200

    def test_negative_amount_does_not_save(self, client, db):
        _set_income('5000')
        h = _make_category('Housing')
        client.post('/budgets/recommend', data={f'amount_{h.id}': '-100'})
        assert Budget.query.count() == 0
