"""GET /api/budgets — Story 4.4."""
import json
from decimal import Decimal
from datetime import date

import pytest

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


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


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


def _make_txn(acct, cat, amount, is_credit=False, month_offset=0):
    today = date.today()
    month = today.month + month_offset
    year = today.year
    if month < 1:
        month += 12
        year -= 1
    elif month > 12:
        month -= 12
        year += 1
    txn = Transaction(
        date=f'{year}-{month:02d}-15',
        merchant_normalized='TestMerchant',
        amount=Decimal(str(amount)),
        is_credit=is_credit,
        is_manual=True,
        account_id=acct.id,
        category_id=cat.id,
    )
    _db.session.add(txn)
    _db.session.commit()
    return txn


class TestApiBudgets:
    def test_returns_200(self, client, db):
        assert client.get('/api/budgets').status_code == 200

    def test_content_type_is_json(self, client, db):
        response = client.get('/api/budgets')
        assert response.content_type.startswith('application/json')

    def test_returns_list(self, client, db):
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        assert isinstance(data, list)

    def test_item_has_required_keys(self, client, db):
        _make_category('Housing')
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        assert len(data) > 0
        item = data[0]
        assert 'category' in item
        assert 'budgeted' in item
        assert 'spent' in item

    def test_budgeted_amount_correct(self, client, db):
        cat = _make_category('Housing')
        _make_budget(cat, '800.00')
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        housing = next(d for d in data if d['category'] == 'Housing')
        assert housing['budgeted'] == 800.0

    def test_spent_amount_correct(self, client, db):
        acct = _make_account()
        cat = _make_category('Dining')
        _make_budget(cat, '300.00')
        _make_txn(acct, cat, '75.00')
        _make_txn(acct, cat, '45.00')
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        dining = next(d for d in data if d['category'] == 'Dining')
        assert dining['spent'] == 120.0

    def test_no_budget_shows_zero_budgeted(self, client, db):
        _make_category('Entertainment')
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        ent = next(d for d in data if d['category'] == 'Entertainment')
        assert ent['budgeted'] == 0

    def test_no_transactions_shows_zero_spent(self, client, db):
        cat = _make_category('Utilities')
        _make_budget(cat, '150.00')
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        util = next(d for d in data if d['category'] == 'Utilities')
        assert util['spent'] == 0

    def test_excludes_credit_transactions(self, client, db):
        acct = _make_account()
        cat = _make_category('Groceries')
        _make_budget(cat, '400.00')
        _make_txn(acct, cat, '50.00', is_credit=True)
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        groc = next(d for d in data if d['category'] == 'Groceries')
        assert groc['spent'] == 0

    def test_excludes_prior_month_transactions(self, client, db):
        acct = _make_account()
        cat = _make_category('Shopping')
        _make_budget(cat, '200.00')
        _make_txn(acct, cat, '99.00', month_offset=-1)
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        shop = next(d for d in data if d['category'] == 'Shopping')
        assert shop['spent'] == 0

    def test_inactive_category_not_included(self, client, db):
        cat = Category(name='DeadCat', is_system=False, is_active=False)
        _db.session.add(cat)
        _db.session.commit()
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        names = [d['category'] for d in data]
        assert 'DeadCat' not in names

    def test_sql_aggregation_multiple_txns(self, client, db):
        acct = _make_account()
        cat = _make_category('Transportation')
        _make_budget(cat, '500.00')
        for amt in ['30.00', '45.00', '20.00']:
            _make_txn(acct, cat, amt)
        response = client.get('/api/budgets')
        data = json.loads(response.data)
        trans = next(d for d in data if d['category'] == 'Transportation')
        assert trans['spent'] == 95.0
