"""
Tests for bug #7 — /api/summary must agree with the executor's cash model.

Regression: /api/summary computed total_trades as len() over a LIMIT-500
query (capped once history grows) and reimplemented cash as
bankroll - sum(net_cost of open trades) from that same capped query.

Fix: counts come from COUNT(*) (compute_metrics_from_db) and cash comes from
cash_available() — the same helper PaperExecutor.initialize() uses — fed by
the same source (get_open_position_data). This test runs both consumers
against one fake DB state and asserts they report identical cash.
"""
import asyncio

import pytest

import api.main as api_main
from bot.executor.paper import PaperExecutor, cash_available


BANKROLL = 10_000.0  # PAPER_BANKROLL default used by both bot and API


class FakeDB:
    """One DB state served to both the API endpoint and the executor."""

    def __init__(self, positions: dict[str, float], total_net_cost: float,
                 total_trades: int, open_count: int):
        self._positions = positions
        self._total_net_cost = total_net_cost
        self._total = total_trades
        self._open = open_count

    # Shared source: executor.initialize() and /api/summary both call this.
    async def get_open_position_data(self):
        return dict(self._positions), self._total_net_cost

    # /api/summary only:
    async def get_metrics_history(self, days=1):
        return []

    async def compute_metrics_from_db(self):
        return {
            "total_trades": self._total,
            "open_count": self._open,
            "closed_count": self._total - self._open,
            "resolved_count": 0,
        }

    async def get_recently_closed_inverted(self, hours=24):
        return set()

    async def get_legacy_incomplete_count(self):
        return 0

    async def get_daily_pnl_closes(self):
        return []


def _run(db: FakeDB, monkeypatch) -> tuple[dict, PaperExecutor]:
    monkeypatch.setattr(api_main, "db", db)
    monkeypatch.delenv("PAPER_BANKROLL", raising=False)

    async def run():
        summary = await api_main.get_summary()
        ex = PaperExecutor(db=db, bankroll=BANKROLL)
        await ex.initialize()
        return summary, ex

    return asyncio.run(run())


def test_api_and_executor_report_same_cash(monkeypatch):
    db = FakeDB(
        positions={"m1": 100.0, "m2": 80.0},
        total_net_cost=183.60,   # 180 + fees
        total_trades=12,
        open_count=2,
    )
    summary, ex = _run(db, monkeypatch)
    assert summary["cash_available"] == pytest.approx(ex.get_portfolio().cash)
    assert summary["cash_available"] == pytest.approx(
        cash_available(BANKROLL, 183.60)
    )
    assert summary["total_deployed"] == pytest.approx(183.60)


def test_total_trades_not_capped_by_query_limit(monkeypatch):
    """700 trades in DB: the old len(LIMIT 500) reported 500."""
    db = FakeDB(
        positions={"m1": 100.0},
        total_net_cost=102.0,
        total_trades=700,
        open_count=1,
    )
    summary, _ = _run(db, monkeypatch)
    assert summary["total_trades"] == 700
    assert summary["open_trades_count"] == 1
    assert summary["closed_trades_count"] == 699


def test_cash_consistency_with_no_open_positions(monkeypatch):
    db = FakeDB(positions={}, total_net_cost=0.0, total_trades=0, open_count=0)
    summary, ex = _run(db, monkeypatch)
    assert summary["cash_available"] == pytest.approx(BANKROLL)
    assert ex.get_portfolio().cash == pytest.approx(BANKROLL)
