"""Paper trading API endpoints — full session management."""
from __future__ import annotations

import datetime as dt
import os
from pathlib import Path
from typing import Any

from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel

from apps.web.dependencies import get_project_root
from apps.web import paper_trading_service as svc

router = APIRouter(prefix="/paper", tags=["paper_trading"])


# ── Helpers ───────────────────────────────────────────────────────────────────

def _get_db_path() -> str:
    db = os.environ.get("PAPER_TRADER_DB", "paper_trading.db")
    p = Path(db)
    if not p.is_absolute():
        p = get_project_root() / db
    return str(p)


def _get_state_manager():
    from apps.paper_trader.state import StateManager
    return StateManager(_get_db_path())


def _get_broker():
    from apps.paper_trader.alpaca_broker import AlpacaBroker
    return AlpacaBroker.from_env()


def _session_summary(session, state) -> dict[str, Any]:
    """Build a JSON-serializable summary of a session."""
    snapshots = state.list_snapshots(session.session_id)
    session_st = state.get_session_state(session.session_id)
    latest = snapshots[-1] if snapshots else None
    initial = session.initial_equity
    current = float(latest["equity"]) if latest else initial
    peak = state.get_peak_equity(session.session_id, initial)
    drawdown_pct = max(0.0, (peak - current) / peak * 100) if peak > 0 else 0.0
    total_pnl = current - initial
    total_pnl_pct = total_pnl / initial * 100 if initial else 0.0
    trades = state.list_trades(session.session_id)
    return {
        "session_id": session.session_id,
        "session_name": session.session_name,
        "config_path": session.config_path,
        "initial_equity": initial,
        "created_at": session.created_at,
        "status": session.status,
        "parking_preset": session.parking_preset,
        "idle_alpha_preset": session.idle_alpha_preset,
        "form4_sleeve_preset": session.form4_sleeve_preset,
        "ownership_sleeve_preset": session.ownership_sleeve_preset,
        "risk_off_alpha_sleeve_preset": getattr(session, "risk_off_alpha_sleeve_preset", None),
        "current_equity": current,
        "peak_equity": peak,
        "total_pnl": total_pnl,
        "total_pnl_pct": total_pnl_pct,
        "drawdown_pct": drawdown_pct,
        "trade_count": len(trades),
        "latest_date": latest["date"] if latest else None,
        "kill_switch": bool(session_st.kill_switch_triggered),
        "cooldown_remaining": session_st.cooldown_remaining,
        "consecutive_losses": session_st.consecutive_losses,
        "daily_new_risk_used": session_st.daily_new_risk_used,
        "last_processed_date": session_st.last_processed_date,
    }


def _auto_schedule_info() -> list[dict[str, Any]]:
    """Return schedule with countdown info (server-side computation)."""
    from zoneinfo import ZoneInfo
    TZ_ET = ZoneInfo("America/New_York")
    SCHEDULE = [
        {"name": "pipeline_pre",   "et_hour": 7,  "et_min": 0,  "label": "Pre-market pipeline"},
        {"name": "run_open",        "et_hour": 9,  "et_min": 35, "label": "run-open (장 시작 직후)"},
        {"name": "run_close",       "et_hour": 15, "et_min": 45, "label": "run-close (장 마감 직전)"},
        {"name": "pipeline_post",   "et_hour": 16, "et_min": 30, "label": "Post-close pipeline"},
    ]
    now_et = dt.datetime.now(tz=TZ_ET)
    today = now_et.date()
    result = []
    for ev in SCHEDULE:
        et_dt = dt.datetime(today.year, today.month, today.day,
                            ev["et_hour"], ev["et_min"], tzinfo=TZ_ET)
        wait_secs = (et_dt - now_et).total_seconds()
        result.append({
            "name": ev["name"],
            "label": ev["label"],
            "et_time": et_dt.strftime("%I:%M %p ET"),
            "wait_secs": max(0.0, wait_secs),
        })
    return result


# ── Sessions ──────────────────────────────────────────────────────────────────

@router.get("/sessions")
def list_sessions() -> dict[str, Any]:
    state = _get_state_manager()
    sessions = state.list_sessions()
    return {"sessions": [_session_summary(s, state) for s in sessions]}


class CreateSessionRequest(BaseModel):
    name: str
    config: str   # experiment name, numeric ID, or config path
    capital: float = 10000.0
    parking: str | None = None  # optional parking preset name
    idle_alpha: str | None = None  # optional idle alpha sleeve preset name
    form4_sleeve: str | None = None  # optional Form 4 sleeve preset name
    ownership_sleeve: str | None = None  # optional 13D/13G ownership sleeve preset name
    risk_off_sleeve: str | None = None  # optional risk-off alpha sleeve preset name


@router.post("/sessions")
def create_session(req: CreateSessionRequest) -> dict[str, Any]:
    from apps.paper_trader.cli import _resolve_config_path

    config_path = _resolve_config_path(req.config)
    abs_path = Path(config_path)
    if not abs_path.is_absolute():
        abs_path = get_project_root() / config_path
    if not abs_path.exists():
        raise HTTPException(status_code=400, detail=f"Config not found: {config_path}")
    config_path = str(config_path)   # keep relative for storage

    # Validate parking preset if provided
    if req.parking:
        try:
            from libs.backtest.domain import PARKING_PRESETS
            if req.parking not in PARKING_PRESETS:
                raise HTTPException(status_code=400, detail=f"Unknown parking preset: {req.parking}")
        except ImportError:
            pass
    if req.idle_alpha:
        try:
            from libs.backtest.domain import IDLE_ALPHA_SLEEVE_PRESETS
            if req.idle_alpha not in IDLE_ALPHA_SLEEVE_PRESETS:
                raise HTTPException(status_code=400, detail=f"Unknown idle alpha preset: {req.idle_alpha}")
        except ImportError:
            pass
    if req.form4_sleeve:
        try:
            from libs.backtest.domain import FORM4_CAPTURE_SLEEVE_PRESETS
            if req.form4_sleeve not in FORM4_CAPTURE_SLEEVE_PRESETS:
                raise HTTPException(status_code=400, detail=f"Unknown Form 4 sleeve preset: {req.form4_sleeve}")
        except ImportError:
            pass
    if req.ownership_sleeve:
        try:
            from libs.backtest.domain import OWNERSHIP_CAPTURE_SLEEVE_PRESETS
            if req.ownership_sleeve not in OWNERSHIP_CAPTURE_SLEEVE_PRESETS:
                raise HTTPException(status_code=400, detail=f"Unknown ownership sleeve preset: {req.ownership_sleeve}")
        except ImportError:
            pass
    if req.risk_off_sleeve:
        try:
            from libs.backtest.domain import RISK_OFF_ALPHA_SLEEVE_PRESETS
            if req.risk_off_sleeve not in RISK_OFF_ALPHA_SLEEVE_PRESETS:
                raise HTTPException(status_code=400, detail=f"Unknown risk-off sleeve preset: {req.risk_off_sleeve}")
        except ImportError:
            pass

    state = _get_state_manager()
    if state.get_session(req.name) is not None:
        raise HTTPException(status_code=409, detail=f"Session '{req.name}' already exists")

    session_id = state.create_session(
        session_name=req.name,
        config_path=config_path,
        initial_equity=req.capital,
        parking_preset=req.parking or None,
        idle_alpha_preset=req.idle_alpha or None,
        form4_sleeve_preset=req.form4_sleeve or None,
        ownership_sleeve_preset=req.ownership_sleeve or None,
        risk_off_alpha_sleeve_preset=req.risk_off_sleeve or None,
    )
    return {
        "session_id": session_id,
        "name": req.name,
        "capital": req.capital,
        "config": config_path,
        "parking": req.parking,
        "idle_alpha": req.idle_alpha,
        "form4_sleeve": req.form4_sleeve,
        "ownership_sleeve": req.ownership_sleeve,
        "risk_off_sleeve": req.risk_off_sleeve,
    }


@router.get("/sessions/{session_id}")
def get_session(session_id: str) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail=f"Session not found: {session_id}")
    return _session_summary(session, state)


@router.post("/sessions/{session_id}/pause")
def pause_session(session_id: str) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    if session.status != "active":
        raise HTTPException(status_code=400, detail=f"Session is already {session.status}")
    state.set_session_status(session.session_id, "paused")
    return {"session_id": session_id, "status": "paused"}


@router.post("/sessions/{session_id}/resume")
def resume_session(session_id: str) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    if session.status != "paused":
        raise HTTPException(status_code=400, detail=f"Session is {session.status}, not paused")
    state.set_session_status(session.session_id, "active")
    return {"session_id": session_id, "status": "active"}


@router.delete("/sessions/{session_id}")
def close_session(session_id: str) -> dict[str, Any]:
    """Liquidate all Alpaca positions and delete all session data."""
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")

    orders_closed = 0
    broker_error: str | None = None
    try:
        broker = _get_broker()
        orders = broker.close_all_positions()
        orders_closed = len(orders)
    except Exception as exc:
        broker_error = str(exc)

    for ss in state.get_open_strategy_states(session.session_id):
        state.close_strategy_state(session.session_id, ss.symbol)

    state.delete_session(session.session_id)
    return {
        "deleted": True,
        "session_id": session_id,
        "positions_closed": orders_closed,
        "broker_error": broker_error,
    }


# ── Data endpoints ─────────────────────────────────────────────────────────────

@router.get("/sessions/{session_id}/positions")
def get_positions(session_id: str) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")

    strategy_states = {
        ss.symbol: ss
        for ss in state.get_open_strategy_states(session.session_id)
    }

    try:
        broker = _get_broker()
        positions = broker.list_positions()
        result = []
        for p in sorted(positions, key=lambda x: x.symbol):
            ss = strategy_states.get(p.symbol)
            qty = float(p.qty)
            entry = float(p.avg_entry_price) if p.avg_entry_price else 0.0
            pnl = float(p.unrealized_pl)
            pnl_pct = pnl / (entry * qty) * 100 if entry and qty else 0.0
            result.append({
                "symbol": p.symbol,
                "qty": qty,
                "avg_entry_price": entry,
                "current_price": float(p.current_price) if p.current_price else None,
                "unrealized_pl": pnl,
                "unrealized_pl_pct": pnl_pct,
                "days_held": ss.days_held if ss else None,
                "stop_price": ss.current_stop if ss else None,
                "target_price": ss.target_price if ss else None,
                "entry_date": ss.entry_date if ss else None,
                "engine_id": ss.engine_id if ss else None,
                "trade_direction": ss.trade_direction if ss else None,
            })
        # Include strategy states missing from broker (ghost positions)
        broker_symbols = {p.symbol for p in positions}
        for sym, ss in strategy_states.items():
            if sym not in broker_symbols:
                result.append({
                    "symbol": sym,
                    "qty": None,
                    "avg_entry_price": None,
                    "current_price": None,
                    "unrealized_pl": None,
                    "unrealized_pl_pct": None,
                    "days_held": ss.days_held,
                    "stop_price": ss.current_stop,
                    "target_price": ss.target_price,
                    "entry_date": ss.entry_date,
                    "engine_id": ss.engine_id,
                    "trade_direction": ss.trade_direction,
                    "_ghost": True,
                })
        return {"positions": result, "broker_available": True}
    except Exception as exc:
        result = []
        for sym, ss in sorted(strategy_states.items()):
            result.append({
                "symbol": sym,
                "qty": None,
                "avg_entry_price": None,
                "current_price": None,
                "unrealized_pl": None,
                "unrealized_pl_pct": None,
                "days_held": ss.days_held,
                "stop_price": ss.current_stop,
                "target_price": ss.target_price,
                "entry_date": ss.entry_date,
                "engine_id": ss.engine_id,
                "trade_direction": ss.trade_direction,
            })
        return {"positions": result, "broker_available": False, "broker_error": str(exc)}


@router.get("/sessions/{session_id}/trades")
def get_trades(
    session_id: str,
    last: int | None = Query(None, description="Show last N trades"),
) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    import datetime
    trades = state.list_trades(session.session_id, limit=last)

    # Include parking entries (active + closed) as parking-sleeve trades
    parking_entries = state.list_parking_entries(session.session_id)
    today = datetime.date.today()
    for p in parking_entries:
        entry_date = p.get("entry_date", "")
        try:
            days = (today - datetime.date.fromisoformat(entry_date)).days
        except Exception:
            days = 0
        is_active = p.get("status") == "active"
        trades.append({
            "trade_id": f"parking_{session_id}_{entry_date}_{p['symbol']}",
            "session_id": session_id,
            "symbol": p["symbol"],
            "entry_date": entry_date,
            "exit_date": None if is_active else entry_date,
            "entry_price": p.get("avg_price"),
            "exit_price": None,
            "exit_reason": "active" if is_active else "closed",
            "shares": p.get("qty"),
            "net_pnl": None,
            "r_multiple": None,
            "holding_days": days,
            "engine_id": "cash_parking",
            "capital_bucket_id": "parking",
        })

    return {"trades": trades, "total": len(trades)}


@router.get("/sessions/{session_id}/equity")
def get_equity(session_id: str) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    snapshots = state.list_snapshots(session.session_id)
    return {"snapshots": snapshots, "initial_equity": session.initial_equity}


# ── Run tasks (in-process) ────────────────────────────────────────────────────

class RunRequest(BaseModel):
    date: str | None = None
    force: bool = False


@router.post("/sessions/{session_id}/run")
async def run_daily(session_id: str, req: RunRequest = RunRequest()) -> dict[str, Any]:
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    return await svc.run_task(session, "run", _get_db_path(), date=req.date, force=req.force)


@router.post("/sessions/{session_id}/run-close")
async def run_close(session_id: str, req: RunRequest = RunRequest()) -> dict[str, Any]:
    """장 마감 직전: same-day 이벤트 → MOC 매수."""
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    return await svc.run_task(session, "run-close", _get_db_path(), date=req.date, force=req.force)


@router.post("/sessions/{session_id}/run-open")
async def run_open(session_id: str, req: RunRequest = RunRequest()) -> dict[str, Any]:
    """장 시작 직후: 전날 exit + after-close 이벤트 → 시장가 매수."""
    state = _get_state_manager()
    session = state.get_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    return await svc.run_task(session, "run-open", _get_db_path(), date=req.date, force=req.force)


@router.post("/run-all")
async def run_all_sessions() -> dict[str, Any]:
    """Run daily processing for all active sessions."""
    state = _get_state_manager()
    active = [s for s in state.list_sessions() if s.status == "active"]
    if not active:
        return {"tasks": [], "message": "No active sessions found"}
    task = await svc.run_all_task(active, _get_db_path())
    return {"tasks": [task]}


# ── Task management ────────────────────────────────────────────────────────────

@router.get("/tasks")
def list_tasks(session_name: str | None = Query(None)) -> dict[str, Any]:
    tasks = svc.list_tasks(session_name=session_name)
    # Don't send full log in list view
    slim = [{k: v for k, v in t.items() if k != "log"} for t in tasks]
    return {"tasks": slim}


@router.get("/tasks/{task_id}")
def get_task(task_id: str) -> dict[str, Any]:
    task = svc.get_task(task_id)
    if task is None:
        raise HTTPException(status_code=404, detail="Task not found")
    return dict(task)


@router.get("/tasks/{task_id}/log")
def get_task_log(task_id: str) -> dict[str, Any]:
    result = svc.get_task_log(task_id)
    if result is None:
        raise HTTPException(status_code=404, detail="Task not found")
    return result


# ── Utility ────────────────────────────────────────────────────────────────────

@router.get("/configs")
def list_configs(
    status: str | None = Query(None, description="Filter by status (active/retired)"),
    pattern: str | None = Query(None, description="Filter by name pattern"),
    limit: int = Query(200, le=1000),
) -> dict[str, Any]:
    """List available experiment configs for session creation."""
    configs_dir = get_project_root() / "configs" / "experiments"
    if not configs_dir.exists():
        return {"configs": [], "total": 0}

    import json
    index_path = configs_dir / ".index.json"
    configs = []
    if index_path.exists():
        try:
            index = json.loads(index_path.read_text())
            for name, meta in sorted(index.get("experiments", {}).items(), key=lambda x: x[0]):
                cfg_path = configs_dir / f"{name}.json"
                if not cfg_path.exists():
                    continue
                s = meta.get("status", "active")
                if status and s != status:
                    continue
                if pattern and pattern.lower() not in name.lower():
                    continue
                configs.append({
                    "name": name,
                    "path": f"configs/experiments/{name}.json",
                    "id": meta.get("id"),
                    "status": s,
                    "version_family": meta.get("version_family"),
                    "generation": meta.get("generation"),
                    "parent": meta.get("parent"),
                })
        except Exception:
            pass

    if not configs:
        for f in sorted(configs_dir.glob("*.json")):
            if f.name.startswith("."):
                continue
            if pattern and pattern.lower() not in f.stem.lower():
                continue
            configs.append({
                "name": f.stem,
                "path": f"configs/experiments/{f.name}",
                "id": None,
                "status": "active",
                "version_family": None,
                "generation": None,
                "parent": None,
            })

    total = len(configs)
    return {"configs": configs[:limit], "total": total}


# ── Auto scheduler endpoints ──────────────────────────────────────────────────

class AutoStartRequest(BaseModel):
    sessions: list[str] = []   # empty = all active
    dry_run: bool = False


@router.get("/auto")
def get_auto_status() -> dict[str, Any]:
    """Get auto scheduler status + today's schedule."""
    scheduler = svc.auto_scheduler
    return {
        "running": scheduler.running,
        "pid": None,           # in-process: no separate PID
        "source": "in_process" if scheduler.running else None,
        "schedule": _auto_schedule_info(),
        "log_tail": scheduler.get_log_tail(80),
        "log_lines": scheduler.log_line_count,
    }


@router.post("/auto/start")
async def start_auto(req: AutoStartRequest) -> dict[str, Any]:
    """Start the in-process auto scheduler. Returns 409 if already running."""
    scheduler = svc.auto_scheduler
    if scheduler.running:
        raise HTTPException(
            status_code=409,
            detail="Auto scheduler already running. Stop it first.",
        )
    scheduler.start(sessions=req.sessions, dry_run=req.dry_run, db_path=_get_db_path())
    return {"started": True, "pid": None, "dry_run": req.dry_run, "sessions": req.sessions}


@router.post("/auto/stop")
async def stop_auto() -> dict[str, Any]:
    """Stop the in-process auto scheduler."""
    scheduler = svc.auto_scheduler
    if not scheduler.running:
        raise HTTPException(status_code=409, detail="Auto scheduler is not running.")
    scheduler.stop()
    return {"stopped": True, "pid": None}


@router.get("/auto/log")
def get_auto_log(lines: int = Query(200, le=2000)) -> dict[str, Any]:
    """Get the last N lines of the auto scheduler log."""
    scheduler = svc.auto_scheduler
    return {
        "log": scheduler.get_log(lines),
        "lines": scheduler.log_line_count,
        "source": "in_process",
    }
