"""2-week paper trading simulation using real pipeline DB + MockBroker.

Simulates what would have happened during March 8-21, 2026 for sessions v236 and v4.41.
Uses real EventDetector (connects to PostgreSQL pipeline DB) but replaces Alpaca with
a MockBroker that fills orders using Oracle historical price data.

Usage:
    python -m apps.tools.simulate_2week
"""
from __future__ import annotations

import asyncio
import datetime as dt
import os
import sys
import tempfile
from pathlib import Path
from typing import Any

# ── Load .env ─────────────────────────────────────────────────────────────────
_ENV_FILE = Path(__file__).parent.parent.parent / ".env"
if _ENV_FILE.exists():
    for _line in _ENV_FILE.read_text().splitlines():
        _line = _line.strip()
        if _line and not _line.startswith("#") and "=" in _line:
            _k, _, _v = _line.partition("=")
            os.environ.setdefault(_k.strip(), _v.strip())

# ── Verify Oracle is reachable before heavy imports ───────────────────────────
_ORACLE_URL = os.environ.get("STOCK_ORACLE_URL", "http://localhost:18001")
_POSTGRES_DSN = os.environ.get("POSTGRES_DSN", "")

from rich.console import Console
from rich.table import Table
from rich import box as rbox

console = Console(width=120)


# ─────────────────────────────────────────────────────────────────────────────
# MockBroker + helpers (imported from mock_broker module)
# ─────────────────────────────────────────────────────────────────────────────

from apps.paper_trader.mock_broker import MockBroker, get_event_symbols_from_db, prefetch_bars as _prefetch_bars


async def prefetch_bars(
    symbols: list[str],
    start: dt.date,
    end: dt.date,
    oracle_url: str,
) -> dict[str, dict[dt.date, dict]]:
    return await _prefetch_bars(symbols, start, end, oracle_url, console=console)


# ─────────────────────────────────────────────────────────────────────────────
# Simulation runner
# ─────────────────────────────────────────────────────────────────────────────

async def run_simulation(
    session_name: str,
    config_path: str,
    initial_equity: float,
    trading_days: list[dt.date],
    bars_cache: dict[str, dict[dt.date, dict]],
    db_dsn: str,
    oracle_url: str,
) -> dict[str, Any]:
    """Run phased simulation for one session. Returns summary."""
    import tempfile
    from apps.paper_trader.state import StateManager, SessionRow
    from apps.paper_trader.event_detector import EventDetector
    from apps.paper_trader.engine import PaperTradingEngine

    # Temp SQLite DB (won't touch production DB)
    tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
    tmp.close()
    tmp_db = tmp.name

    state = StateManager(tmp_db)
    session_id = state.create_session(
        session_name=session_name,
        config_path=config_path,
        initial_equity=initial_equity,
    )
    session = state.get_session(session_id)

    broker = MockBroker(initial_equity=initial_equity, bars_cache=bars_cache)
    detector = EventDetector(db_dsn=db_dsn, oracle_url=oracle_url)
    engine = PaperTradingEngine(
        session=session,
        broker=broker,
        state=state,
        event_detector=detector,
    )

    all_entries: list[dict] = []
    all_exits: list[dict] = []
    daily_summaries: list[dict] = []

    console.print(f"\n[bold cyan]Session: {session_name}[/]  config: {config_path}")
    console.print(f"  Equity: ${initial_equity:,.0f}  |  Days: {trading_days[0]} → {trading_days[-1]}\n")

    for day in trading_days:
        # ── run-open: exits + after-close entries ──────────────────────────
        broker.set_sim_context(day, "open")
        open_result = await engine.run_next_open(target_date=day, force=True)

        # ── run-close: same-day entries ────────────────────────────────────
        broker.set_sim_context(day, "close")
        close_result = await engine.run_reaction_close(target_date=day, force=True)

        acct = broker.get_account()
        exits_today = open_result.get("exits", [])
        entries_open = open_result.get("entries", [])
        entries_close = close_result.get("entries", [])
        candidates_open = open_result.get("candidates_detected", 0)
        candidates_close = close_result.get("candidates_detected", 0)
        rejected_open = open_result.get("rejected", [])
        rejected_close = close_result.get("rejected", [])

        for e in exits_today:
            e["exit_date"] = day.isoformat()
        for e in entries_open:
            e["entry_date"] = day.isoformat()
            e["convention"] = "next_open"
        for e in entries_close:
            e["entry_date"] = day.isoformat()
            e["convention"] = "reaction_close"

        all_exits.extend(exits_today)
        all_entries.extend(entries_open + entries_close)

        summary = {
            "date": day,
            "equity": acct.equity,
            "cash": acct.cash,
            "market_value": acct.long_market_value,
            "positions": len(broker.list_positions()),
            "exits": len(exits_today),
            "entries": len(entries_open) + len(entries_close),
            "candidates": candidates_open + candidates_close,
            "rejected": len(rejected_open) + len(rejected_close),
        }
        daily_summaries.append(summary)

        pnl_today = sum(e.get("pnl", 0) for e in exits_today)
        day_str = f"[bold]{day.strftime('%a %m/%d')}[/]"
        eq_str = f"${acct.equity:,.2f}"
        entries_str = f"{len(entries_open)+len(entries_close)} entries"
        exits_str = f"{len(exits_today)} exits"
        cand_str = f"{candidates_open + candidates_close} candidates"
        pos_str = f"{len(broker.list_positions())} pos"

        details = []
        for e in entries_open + entries_close:
            details.append(f"  [green]  ↳ ENTER {e['symbol']} ({e.get('event_type','?')}, score={e.get('score', 0):.2f}) "
                          f"× {e['shares']} shares  [{e['convention']}][/]")
        for e in exits_today:
            pnl_col = "green" if e.get("pnl", 0) >= 0 else "red"
            details.append(f"  [{pnl_col}]  ↳ EXIT  {e['symbol']} ({e['reason']})  "
                          f"P&L=${e.get('pnl', 0):+,.2f}[/{pnl_col}]")
        for r in (rejected_open + rejected_close)[:3]:  # show first 3 rejections
            details.append(f"  [dim]  ↳ skip  {r['symbol']} → {r['reason']}[/]")

        console.print(f"  {day_str}  {eq_str}  {entries_str}  {exits_str}  {cand_str}  {pos_str}")
        for d in details:
            console.print(d)

    # ── Final summary ──────────────────────────────────────────────────────
    acct_final = broker.get_account()
    total_pnl = acct_final.equity - initial_equity
    pct = total_pnl / initial_equity * 100

    console.print(f"\n  [bold]Final:[/]  equity=${acct_final.equity:,.2f}  "
                  f"P&L=${total_pnl:+,.2f} ({pct:+.2f}%)  "
                  f"trades={len(all_entries)} entries / {len(all_exits)} exits")

    # Print trade table
    if all_entries or all_exits:
        tbl = Table(box=rbox.SIMPLE, show_header=True, header_style="bold yellow", padding=(0, 1))
        tbl.add_column("Date", style="dim")
        tbl.add_column("Action", style="bold")
        tbl.add_column("Symbol")
        tbl.add_column("Event Type")
        tbl.add_column("Shares", justify="right")
        tbl.add_column("Score", justify="right")
        tbl.add_column("Convention / Reason")

        for e in sorted(all_entries, key=lambda x: x["entry_date"]):
            tbl.add_row(
                e["entry_date"], "[green]ENTER[/]", e["symbol"],
                e.get("event_type", "?"),
                str(e.get("shares", "?")),
                f"{e.get('score', 0):.2f}",
                e.get("convention", "?"),
            )
        for e in sorted(all_exits, key=lambda x: x.get("exit_date", "")):
            pnl = e.get("pnl", 0)
            pnl_col = "green" if pnl >= 0 else "red"
            tbl.add_row(
                e.get("exit_date", "?"), f"[{pnl_col}]EXIT[/{pnl_col}]", e["symbol"],
                "",
                str(abs(int(e.get("shares", 0)))),
                f"[{pnl_col}]{pnl:+,.2f}[/{pnl_col}]",
                e.get("reason", "?"),
            )
        console.print(tbl)

    # Clean up temp DB
    try:
        Path(tmp_db).unlink()
    except Exception:
        pass

    return {
        "session": session_name,
        "total_pnl": total_pnl,
        "total_pnl_pct": pct,
        "entries": len(all_entries),
        "exits": len(all_exits),
        "final_equity": acct_final.equity,
        "daily": daily_summaries,
    }


# ─────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────

SESSIONS = [
    {
        "name": "v236",
        "config": "configs/experiments/return_max_long_v1.236.json",
        "equity": 10000.0,
    },
    {
        "name": "v4.41",
        "config": "configs/experiments/return_max_long_v4.41.json",
        "equity": 10000.0,
    },
]

SIM_START = dt.date(2026, 2, 22)
SIM_END = dt.date(2026, 3, 21)


async def main() -> None:
    from libs.common.time_utils import is_trading_day

    console.print("[bold cyan]═══ 1-Month Paper Trading Simulation (Feb 22 – Mar 21, 2026) ═══[/]")
    console.print(f"Oracle URL : {_ORACLE_URL}")
    console.print(f"Postgres   : {_POSTGRES_DSN[:50]}..." if len(_POSTGRES_DSN) > 50 else f"Postgres   : {_POSTGRES_DSN}")

    # ── Trading days ───────────────────────────────────────────────────────
    all_days = [
        SIM_START + dt.timedelta(days=i)
        for i in range((SIM_END - SIM_START).days + 1)
    ]
    trading_days = [d for d in all_days if is_trading_day(d)]
    console.print(f"Trading days: {[d.isoformat() for d in trading_days]}\n")

    # ── Get symbols from pipeline DB ───────────────────────────────────────
    console.print("[bold]Fetching event symbols from pipeline DB...[/]")
    if not _POSTGRES_DSN:
        console.print("[red]ERROR: POSTGRES_DSN not set! Cannot query pipeline DB.[/]")
        sys.exit(1)

    event_symbols = await get_event_symbols_from_db(
        _POSTGRES_DSN,
        start_date=SIM_START - dt.timedelta(days=7),  # buffer for label lag
        end_date=SIM_END,
        console=console,
    )
    console.print(f"  Found {len(event_symbols)} unique symbols in events")

    # ── Pre-fetch bars ─────────────────────────────────────────────────────
    all_symbols = sorted(set(event_symbols + ["SPY", "QQQ"]))
    bar_start = SIM_START - dt.timedelta(days=90)  # enough history for exits + macro SMA

    console.print(f"\n[bold]Pre-fetching bars for {len(all_symbols)} symbols ({bar_start} → {SIM_END})...[/]")
    bars_cache = await prefetch_bars(all_symbols, bar_start, SIM_END, _ORACLE_URL)
    fetched = sum(1 for v in bars_cache.values() if v)
    console.print(f"  Fetched bars for {fetched}/{len(all_symbols)} symbols")

    if fetched == 0:
        console.print("[red]ERROR: No bars fetched. Is Oracle running at {_ORACLE_URL}?[/]")
        sys.exit(1)

    # ── Suppress verbose logs during simulation ────────────────────────────
    import logging
    logging.getLogger("apps.paper_trader").setLevel(logging.WARNING)
    logging.getLogger("libs.backtest").setLevel(logging.WARNING)

    # ── Run simulations ────────────────────────────────────────────────────
    console.print("\n[bold]Running simulations...[/]\n" + "═" * 80)

    results = []
    for sess in SESSIONS:
        result = await run_simulation(
            session_name=sess["name"],
            config_path=sess["config"],
            initial_equity=sess["equity"],
            trading_days=trading_days,
            bars_cache=bars_cache,
            db_dsn=_POSTGRES_DSN,
            oracle_url=_ORACLE_URL,
        )
        results.append(result)
        console.print("─" * 80)

    # ── Final comparison table ─────────────────────────────────────────────
    console.print("\n[bold cyan]═══ Final Results ═══[/]")
    tbl = Table(box=rbox.SIMPLE, show_header=True, header_style="bold yellow", padding=(0, 2))
    tbl.add_column("Session")
    tbl.add_column("Final Equity", justify="right")
    tbl.add_column("P&L", justify="right")
    tbl.add_column("P&L %", justify="right")
    tbl.add_column("Entries", justify="right")
    tbl.add_column("Exits", justify="right")

    for r in results:
        pnl_col = "green" if r["total_pnl"] >= 0 else "red"
        tbl.add_row(
            r["session"],
            f"${r['final_equity']:,.2f}",
            f"[{pnl_col}]${r['total_pnl']:+,.2f}[/{pnl_col}]",
            f"[{pnl_col}]{r['total_pnl_pct']:+.2f}%[/{pnl_col}]",
            str(r["entries"]),
            str(r["exits"]),
        )
    console.print(tbl)


if __name__ == "__main__":
    asyncio.run(main())
