"""Tests for playbook persistence (PlaybookStore + DB schema)."""

from __future__ import annotations

from datetime import date

import pytest

from src.db import init_db
from src.strategy.models import (
    DayPlaybook,
    GlobalRule,
    MarketOutlook,
    PlaybookStatus,
    ScenarioAction,
    StockCondition,
    StockPlaybook,
    StockScenario,
)
from src.strategy.playbook_store import PlaybookStore


@pytest.fixture
def conn():
    """Create an in-memory DB with schema."""
    connection = init_db(":memory:")
    yield connection
    connection.close()


@pytest.fixture
def store(conn) -> PlaybookStore:
    return PlaybookStore(conn)


def _make_playbook(
    target_date: date = date(2026, 2, 8),
    market: str = "KR",
    outlook: MarketOutlook = MarketOutlook.NEUTRAL,
    stock_codes: list[str] | None = None,
) -> DayPlaybook:
    """Create a test playbook with sensible defaults."""
    if stock_codes is None:
        stock_codes = ["005930"]
    return DayPlaybook(
        date=target_date,
        market=market,
        market_outlook=outlook,
        token_count=150,
        stock_playbooks=[
            StockPlaybook(
                stock_code=code,
                scenarios=[
                    StockScenario(
                        condition=StockCondition(rsi_below=30.0),
                        action=ScenarioAction.BUY,
                        confidence=85,
                        rationale=f"Oversold bounce for {code}",
                    ),
                ],
            )
            for code in stock_codes
        ],
        global_rules=[
            GlobalRule(
                condition="portfolio_pnl_pct < -2.0",
                action=ScenarioAction.REDUCE_ALL,
                rationale="Near circuit breaker",
            ),
        ],
    )


# ---------------------------------------------------------------------------
# Schema
# ---------------------------------------------------------------------------


class TestSchema:
    def test_playbooks_table_exists(self, conn) -> None:
        row = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='playbooks'"
        ).fetchone()
        assert row is not None

    def test_unique_constraint(self, store: PlaybookStore) -> None:
        pb = _make_playbook()
        store.save(pb)
        # Saving again for same date+market should replace, not error
        pb2 = _make_playbook(stock_codes=["005930", "000660"])
        store.save(pb2)
        loaded = store.load(date(2026, 2, 8), "KR")
        assert loaded is not None
        assert loaded.stock_count == 2


# ---------------------------------------------------------------------------
# Save / Load
# ---------------------------------------------------------------------------


class TestSaveLoad:
    def test_save_and_load(self, store: PlaybookStore) -> None:
        pb = _make_playbook()
        row_id = store.save(pb)
        assert row_id > 0

        loaded = store.load(date(2026, 2, 8), "KR")
        assert loaded is not None
        assert loaded.date == date(2026, 2, 8)
        assert loaded.market == "KR"
        assert loaded.stock_count == 1
        assert loaded.scenario_count == 1

    def test_load_not_found(self, store: PlaybookStore) -> None:
        result = store.load(date(2026, 1, 1), "KR")
        assert result is None

    def test_save_preserves_all_fields(self, store: PlaybookStore) -> None:
        pb = _make_playbook(
            outlook=MarketOutlook.BULLISH,
            stock_codes=["005930", "AAPL"],
        )
        store.save(pb)
        loaded = store.load(date(2026, 2, 8), "KR")
        assert loaded is not None
        assert loaded.market_outlook == MarketOutlook.BULLISH
        assert loaded.stock_count == 2
        assert loaded.global_rules[0].action == ScenarioAction.REDUCE_ALL
        assert loaded.token_count == 150

    def test_save_different_markets(self, store: PlaybookStore) -> None:
        kr = _make_playbook(market="KR")
        us = _make_playbook(market="US", stock_codes=["AAPL"])
        store.save(kr)
        store.save(us)

        kr_loaded = store.load(date(2026, 2, 8), "KR")
        us_loaded = store.load(date(2026, 2, 8), "US")
        assert kr_loaded is not None
        assert us_loaded is not None
        assert kr_loaded.market == "KR"
        assert us_loaded.market == "US"
        assert kr_loaded.stock_playbooks[0].stock_code == "005930"
        assert us_loaded.stock_playbooks[0].stock_code == "AAPL"

    def test_save_different_dates(self, store: PlaybookStore) -> None:
        d1 = _make_playbook(target_date=date(2026, 2, 7))
        d2 = _make_playbook(target_date=date(2026, 2, 8))
        store.save(d1)
        store.save(d2)

        assert store.load(date(2026, 2, 7), "KR") is not None
        assert store.load(date(2026, 2, 8), "KR") is not None

    def test_replace_updates_data(self, store: PlaybookStore) -> None:
        pb1 = _make_playbook(outlook=MarketOutlook.BEARISH)
        store.save(pb1)

        pb2 = _make_playbook(outlook=MarketOutlook.BULLISH)
        store.save(pb2)

        loaded = store.load(date(2026, 2, 8), "KR")
        assert loaded is not None
        assert loaded.market_outlook == MarketOutlook.BULLISH


# ---------------------------------------------------------------------------
# Status
# ---------------------------------------------------------------------------


class TestStatus:
    def test_get_status(self, store: PlaybookStore) -> None:
        store.save(_make_playbook())
        status = store.get_status(date(2026, 2, 8), "KR")
        assert status == PlaybookStatus.READY

    def test_get_status_not_found(self, store: PlaybookStore) -> None:
        assert store.get_status(date(2026, 1, 1), "KR") is None

    def test_update_status(self, store: PlaybookStore) -> None:
        store.save(_make_playbook())
        updated = store.update_status(date(2026, 2, 8), "KR", PlaybookStatus.EXPIRED)
        assert updated is True

        status = store.get_status(date(2026, 2, 8), "KR")
        assert status == PlaybookStatus.EXPIRED

    def test_update_status_not_found(self, store: PlaybookStore) -> None:
        updated = store.update_status(date(2026, 1, 1), "KR", PlaybookStatus.FAILED)
        assert updated is False


# ---------------------------------------------------------------------------
# Match count
# ---------------------------------------------------------------------------


class TestMatchCount:
    def test_increment_match_count(self, store: PlaybookStore) -> None:
        store.save(_make_playbook())
        store.increment_match_count(date(2026, 2, 8), "KR")
        store.increment_match_count(date(2026, 2, 8), "KR")

        stats = store.get_stats(date(2026, 2, 8), "KR")
        assert stats is not None
        assert stats["match_count"] == 2

    def test_increment_not_found(self, store: PlaybookStore) -> None:
        result = store.increment_match_count(date(2026, 1, 1), "KR")
        assert result is False


# ---------------------------------------------------------------------------
# Stats
# ---------------------------------------------------------------------------


class TestStats:
    def test_get_stats(self, store: PlaybookStore) -> None:
        store.save(_make_playbook())
        stats = store.get_stats(date(2026, 2, 8), "KR")
        assert stats is not None
        assert stats["status"] == "ready"
        assert stats["token_count"] == 150
        assert stats["scenario_count"] == 1
        assert stats["match_count"] == 0
        assert stats["generated_at"] != ""

    def test_get_stats_not_found(self, store: PlaybookStore) -> None:
        assert store.get_stats(date(2026, 1, 1), "KR") is None


# ---------------------------------------------------------------------------
# List recent
# ---------------------------------------------------------------------------


class TestListRecent:
    def test_list_recent(self, store: PlaybookStore) -> None:
        for day in range(5, 10):
            store.save(_make_playbook(target_date=date(2026, 2, day)))
        results = store.list_recent(market="KR", limit=3)
        assert len(results) == 3
        # Most recent first
        assert results[0]["date"] == "2026-02-09"
        assert results[2]["date"] == "2026-02-07"

    def test_list_recent_all_markets(self, store: PlaybookStore) -> None:
        store.save(_make_playbook(market="KR"))
        store.save(_make_playbook(market="US", stock_codes=["AAPL"]))
        results = store.list_recent(market=None, limit=10)
        assert len(results) == 2

    def test_list_recent_empty(self, store: PlaybookStore) -> None:
        results = store.list_recent(market="KR")
        assert results == []

    def test_list_recent_filter_by_market(self, store: PlaybookStore) -> None:
        store.save(_make_playbook(market="KR"))
        store.save(_make_playbook(market="US", stock_codes=["AAPL"]))
        kr_only = store.list_recent(market="KR")
        assert len(kr_only) == 1
        assert kr_only[0]["market"] == "KR"


# ---------------------------------------------------------------------------
# Delete
# ---------------------------------------------------------------------------


class TestDelete:
    def test_delete(self, store: PlaybookStore) -> None:
        store.save(_make_playbook())
        deleted = store.delete(date(2026, 2, 8), "KR")
        assert deleted is True
        assert store.load(date(2026, 2, 8), "KR") is None

    def test_delete_not_found(self, store: PlaybookStore) -> None:
        deleted = store.delete(date(2026, 1, 1), "KR")
        assert deleted is False

    def test_delete_one_market_keeps_other(self, store: PlaybookStore) -> None:
        store.save(_make_playbook(market="KR"))
        store.save(_make_playbook(market="US", stock_codes=["AAPL"]))
        store.delete(date(2026, 2, 8), "KR")
        assert store.load(date(2026, 2, 8), "KR") is None
        assert store.load(date(2026, 2, 8), "US") is not None
