"""Analytics & Insights — Stories 10.1–10.7."""
from decimal import Decimal
from datetime import date, timedelta

import pytest

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


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

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


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_txn(acct, cat, amount="50.00", merchant="Shop", days_ago=5, is_credit=False):
    d = (date.today() - timedelta(days=days_ago)).isoformat()
    t = Transaction(
        date=d, merchant_normalized=merchant,
        amount=Decimal(amount), is_credit=is_credit, is_manual=True,
        account_id=acct.id, category_id=cat.id,
    )
    _db.session.add(t)
    _db.session.commit()
    return t


def _set_income(amount="3000.00"):
    s = Settings.query.first()
    if s is None:
        s = Settings(id=1, monthly_income=Decimal(amount))
        _db.session.add(s)
    else:
        s.monthly_income = Decimal(amount)
    _db.session.commit()


# ── Story 10.1: Analytics Blueprint Shell ─────────────────────────────────────

class TestAnalyticsShell:
    def test_analytics_returns_200(self, client, db):
        assert client.get("/analytics/").status_code == 200

    def test_analytics_shows_mom_widget(self, client, db):
        response = client.get("/analytics/")
        assert b"Month over Month" in response.data

    def test_analytics_shows_yoy_widget(self, client, db):
        response = client.get("/analytics/")
        assert b"Year over Year" in response.data

    def test_analytics_shows_spending_chart_section(self, client, db):
        response = client.get("/analytics/")
        assert b"Spending by Category" in response.data

    def test_active_page_is_analytics(self, client, db):
        response = client.get("/analytics/")
        assert b"analytics" in response.data.lower()


# ── Story 10.2: Spending Patterns ─────────────────────────────────────────────

class TestSpendingPatterns:
    def test_api_spending_by_category_returns_json(self, client, db):
        response = client.get("/api/analytics/spending-by-category")
        assert response.status_code == 200
        data = response.get_json()
        assert "labels" in data
        assert "categories" in data
        assert "series" in data

    def test_api_spending_by_category_includes_transactions(self, client, db):
        acct = _make_account()
        cat = _make_category("Dining")
        _make_txn(acct, cat, "75.00")
        response = client.get("/api/analytics/spending-by-category")
        data = response.get_json()
        assert "Dining" in data["categories"]

    def test_api_monthly_totals_returns_json(self, client, db):
        response = client.get("/api/analytics/monthly-totals")
        assert response.status_code == 200
        assert isinstance(response.get_json(), list)

    def test_api_monthly_totals_has_expected_fields(self, client, db):
        acct = _make_account()
        cat = _make_category()
        _make_txn(acct, cat, "100.00")
        response = client.get("/api/analytics/monthly-totals")
        data = response.get_json()
        assert len(data) > 0
        assert "year" in data[0]
        assert "month" in data[0]
        assert "total" in data[0]


# ── Story 10.3: MoM / YoY Comparisons ────────────────────────────────────────

class TestMoMYoYComparisons:
    def test_mom_comparison_with_data(self, client, db):
        acct = _make_account()
        cat = _make_category()
        _make_txn(acct, cat, "200.00", days_ago=5)  # this month
        _make_txn(acct, cat, "150.00", days_ago=40)  # last month
        response = client.get("/analytics/")
        assert response.status_code == 200
        assert b"200" in response.data

    def test_yoy_shows_current_year(self, client, db):
        response = client.get("/analytics/")
        assert str(date.today().year).encode() in response.data


# ── Story 10.4: Subscription Detector ────────────────────────────────────────

class TestSubscriptionDetector:
    def test_subscriptions_page_returns_200(self, client, db):
        assert client.get("/analytics/subscriptions").status_code == 200

    def test_subscriptions_empty_state_no_data(self, client, db):
        response = client.get("/analytics/subscriptions")
        assert b"No subscriptions" in response.data

    def test_detects_monthly_subscription(self, client, db):
        from app.services.insights.subscription_detector import detect_subscriptions
        acct = _make_account()
        cat = _make_category()
        # Add 3 monthly charges
        for months_ago in [1, 2, 3]:
            d = (date.today() - timedelta(days=30 * months_ago)).isoformat()
            t = Transaction(
                date=d, merchant_normalized="Netflix",
                amount=Decimal("15.99"), is_credit=False, is_manual=True,
                account_id=acct.id, category_id=cat.id,
            )
            _db.session.add(t)
        _db.session.commit()

        with client.application.app_context():
            subs = detect_subscriptions()
        netflix = next((s for s in subs if s.merchant == "Netflix"), None)
        assert netflix is not None
        assert netflix.recurrence == "monthly"

    def test_subscription_page_shows_detected_subs(self, client, db):
        acct = _make_account()
        cat = _make_category()
        for months_ago in [1, 2, 3]:
            d = (date.today() - timedelta(days=30 * months_ago)).isoformat()
            _db.session.add(Transaction(
                date=d, merchant_normalized="Spotify",
                amount=Decimal("9.99"), is_credit=False, is_manual=True,
                account_id=acct.id, category_id=cat.id,
            ))
        _db.session.commit()
        response = client.get("/analytics/subscriptions")
        assert b"Spotify" in response.data


# ── Story 10.5: Bad Spending Flags ────────────────────────────────────────────

class TestBadSpendingFlags:
    def test_dining_entertainment_flag(self, client, db):
        from app.services.insights.pattern_flags import get_spending_flags
        acct = _make_account()
        dining = _make_category("Dining")
        _set_income("1000.00")
        # Spend 200 on dining this month (> 15% of 1000)
        _make_txn(acct, dining, "200.00", days_ago=2)

        with client.application.app_context():
            flags = get_spending_flags(Decimal("1000.00"))
        dining_flag = next((f for f in flags if f.flag_type == "dining_entertainment_ratio"), None)
        assert dining_flag is not None

    def test_no_flags_when_within_budget(self, client, db):
        from app.services.insights.pattern_flags import get_spending_flags
        acct = _make_account()
        dining = _make_category("Dining")
        _make_txn(acct, dining, "50.00", days_ago=2)

        with client.application.app_context():
            flags = get_spending_flags(Decimal("1000.00"))
        dining_flag = next((f for f in flags if f.flag_type == "dining_entertainment_ratio"), None)
        assert dining_flag is None

    def test_analytics_shows_flags(self, client, db):
        acct = _make_account()
        dining = _make_category("Dining")
        _set_income("500.00")
        _make_txn(acct, dining, "150.00", days_ago=2)
        response = client.get("/analytics/")
        assert response.status_code == 200


# ── Story 10.6: Merchant Spend Summary ───────────────────────────────────────

class TestMerchantSpendSummary:
    def test_merchants_page_returns_200(self, client, db):
        assert client.get("/analytics/merchants").status_code == 200

    def test_merchants_empty_state_no_data(self, client, db):
        response = client.get("/analytics/merchants")
        assert b"No merchant data" in response.data

    def test_merchants_shows_top_merchants(self, client, db):
        acct = _make_account()
        cat = _make_category()
        _make_txn(acct, cat, "100.00", merchant="Amazon", days_ago=2)
        _make_txn(acct, cat, "50.00", merchant="Walmart", days_ago=3)
        response = client.get("/analytics/merchants")
        assert b"Amazon" in response.data
        assert b"Walmart" in response.data

    def test_merchants_date_filter(self, client, db):
        acct = _make_account()
        cat = _make_category()
        _make_txn(acct, cat, "100.00", merchant="RecentShop", days_ago=2)
        _make_txn(acct, cat, "200.00", merchant="OldShop", days_ago=200)
        today = date.today()
        from_date = (today - timedelta(days=30)).isoformat()
        response = client.get(f"/analytics/merchants?from={from_date}")
        assert b"RecentShop" in response.data


# ── Story 10.7: History-Based Budget Recommendations ─────────────────────────

class TestHistoryBasedRecommendations:
    def test_recommendations_page_returns_200(self, client, db):
        assert client.get("/analytics/recommendations").status_code == 200

    def test_no_income_shows_cta(self, client, db):
        response = client.get("/analytics/recommendations")
        assert b"income" in response.data.lower() or b"Settings" in response.data

    def test_insufficient_history_shows_message(self, client, db):
        _set_income()
        response = client.get("/analytics/recommendations")
        assert b"history" in response.data.lower() or b"months" in response.data.lower()

    def test_recommendations_generated_with_history(self, client, db):
        _set_income("3000.00")
        acct = _make_account()
        cat = _make_category("Dining")
        # Two months of data
        for days_ago in [10, 40]:
            _make_txn(acct, cat, "120.00", days_ago=days_ago)
        response = client.get("/analytics/recommendations")
        assert response.status_code == 200

    def test_from_history_follows_budget_recommendation_interface(self, db):
        """Story 10.7: from_history() returns same BudgetRecommendation type as fifty_thirty_twenty()."""
        from app.services.insights.budget_recommender import BudgetRecommendation
        from app.services.analytics.budget_analytics import from_history
        acct = _make_account()
        cat = _make_category("Groceries")
        for days_ago in [10, 40, 70]:
            _make_txn(acct, cat, "100.00", days_ago=days_ago)

        with db.app.app_context() if hasattr(db, 'app') else _db.session.no_autoflush:
            recs = from_history(Decimal("3000.00"))
        if recs:
            for rec in recs:
                assert isinstance(rec, BudgetRecommendation)
                assert isinstance(rec.suggested_amount, Decimal)
