"""Paper-to-live strategy promotion lifecycle."""

from __future__ import annotations

import logging
import os
import sqlite3
import threading
import time
from dataclasses import dataclass
from enum import Enum
from typing import Any

logger = logging.getLogger("horizon.fund.promotion")


class PromotionStage(str, Enum):
    """Strategy promotion stages."""
    PAPER = "paper"
    SHADOW = "shadow"  # Paper with live data, no real capital
    LIVE = "live"


@dataclass
class PromotionCriteria:
    """Minimum criteria for promotion to the next stage."""
    min_paper_days: float = 14.0
    min_trades: int = 50
    min_sharpe: float = 1.0
    max_drawdown_pct: float = 15.0
    min_pnl: float = 0.0

    # Shadow -> Live requires stricter criteria
    min_shadow_days: float = 7.0
    min_shadow_sharpe: float = 1.2


@dataclass(frozen=True)
class PromotionRecord:
    """A recorded promotion event."""
    strategy_name: str
    from_stage: str
    to_stage: str
    timestamp: float
    criteria_met: dict[str, Any]


class PromotionManager:
    """Manages strategy promotion from paper through shadow to live.

    Tracks each strategy's current promotion stage in SQLite and
    evaluates promotion criteria based on StrategyStatus metrics.
    Promotion history is preserved for audit.
    """

    _SCHEMA = """
    CREATE TABLE IF NOT EXISTS promotions (
        strategy_name TEXT PRIMARY KEY,
        stage TEXT NOT NULL DEFAULT 'paper',
        tracked_since REAL NOT NULL,
        last_promoted REAL
    );
    CREATE TABLE IF NOT EXISTS promotion_history (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        strategy_name TEXT NOT NULL,
        from_stage TEXT NOT NULL,
        to_stage TEXT NOT NULL,
        timestamp REAL NOT NULL,
        criteria_json TEXT NOT NULL DEFAULT '{}'
    );
    CREATE INDEX IF NOT EXISTS idx_promo_hist_name ON promotion_history(strategy_name);
    """

    def __init__(
        self,
        criteria: PromotionCriteria | None = None,
        db_path: str | None = None,
    ) -> None:
        self._criteria = criteria or PromotionCriteria()
        self._db_path = db_path or os.path.join(
            os.path.expanduser("~"), ".horizon", "fund_promotions.db"
        )
        db_dir = os.path.dirname(self._db_path)
        if db_dir:
            os.makedirs(db_dir, exist_ok=True)
        self._conn = sqlite3.connect(self._db_path, check_same_thread=False)
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.executescript(self._SCHEMA)
        self._conn.commit()
        if db_dir:
            try:
                os.chmod(self._db_path, 0o600)
            except OSError:
                pass
        self._lock = threading.Lock()
        self._stage_timestamps: dict[str, float] = {}  # strategy_name -> time entered current stage

    def track(self, strategy_name: str, initial_stage: PromotionStage = PromotionStage.PAPER) -> None:
        """Start tracking a strategy for promotion."""
        now = time.time()
        with self._lock:
            self._conn.execute(
                "INSERT OR IGNORE INTO promotions (strategy_name, stage, tracked_since) VALUES (?, ?, ?)",
                (strategy_name, initial_stage.value, now),
            )
            self._conn.commit()
            if strategy_name not in self._stage_timestamps:
                self._stage_timestamps[strategy_name] = now

    def stage(self, strategy_name: str) -> PromotionStage:
        """Get the current promotion stage for a strategy."""
        with self._lock:
            row = self._conn.execute(
                "SELECT stage FROM promotions WHERE strategy_name = ?",
                (strategy_name,),
            ).fetchone()
        if row is None:
            return PromotionStage.PAPER
        return PromotionStage(row[0])

    def check_promotion(
        self,
        strategy_name: str,
        status: Any,
        sharpe: float = 0.0,
    ) -> PromotionStage | None:
        """Check if a strategy is eligible for promotion.

        Args:
            strategy_name: Strategy name.
            status: StrategyStatus with pnl, drawdown_pct, uptime_secs.
            sharpe: Annualized Sharpe ratio (computed externally).

        Returns:
            The new stage if eligible, None otherwise.
        """
        current = self.stage(strategy_name)

        if current == PromotionStage.LIVE:
            return None  # Already at max stage

        c = self._criteria
        # Use per-stage uptime instead of total uptime
        now = time.time()
        stage_entered = self._stage_timestamps.get(strategy_name)
        if stage_entered is None:
            # Fallback: use total uptime if no stage timestamp recorded
            stage_uptime_days = getattr(status, "uptime_secs", 0.0) / 86400.0
        else:
            stage_uptime_days = (now - stage_entered) / 86400.0

        pnl = getattr(status, "pnl", 0.0)
        drawdown = getattr(status, "drawdown_pct", 0.0)
        trades = getattr(status, "total_fills", 0)

        if current == PromotionStage.PAPER:
            # Paper -> Shadow
            if trades < c.min_trades:
                return None
            if (
                stage_uptime_days >= c.min_paper_days
                and pnl >= c.min_pnl
                and drawdown <= c.max_drawdown_pct
                and sharpe >= c.min_sharpe
            ):
                return PromotionStage.SHADOW
        elif current == PromotionStage.SHADOW:
            # Shadow -> Live
            if trades < c.min_trades:
                return None
            if (
                stage_uptime_days >= c.min_shadow_days
                and pnl >= c.min_pnl
                and drawdown <= c.max_drawdown_pct
                and sharpe >= c.min_shadow_sharpe
            ):
                return PromotionStage.LIVE

        return None

    def promote(self, strategy_name: str, new_stage: PromotionStage) -> PromotionRecord:
        """Promote a strategy to a new stage.

        Args:
            strategy_name: Strategy to promote.
            new_stage: Target stage.

        Returns:
            PromotionRecord for the transition.
        """
        now = time.time()
        current = self.stage(strategy_name)

        # Record when the strategy entered the new stage
        self._stage_timestamps[strategy_name] = now

        with self._lock:
            self._conn.execute(
                "UPDATE promotions SET stage = ?, last_promoted = ? WHERE strategy_name = ?",
                (new_stage.value, now, strategy_name),
            )
            import json
            criteria_met = {
                "from": current.value,
                "to": new_stage.value,
            }
            self._conn.execute(
                """INSERT INTO promotion_history
                   (strategy_name, from_stage, to_stage, timestamp, criteria_json)
                   VALUES (?, ?, ?, ?, ?)""",
                (strategy_name, current.value, new_stage.value, now, json.dumps(criteria_met)),
            )
            self._conn.commit()

        logger.info("Promoted '%s': %s -> %s", strategy_name, current.value, new_stage.value)

        return PromotionRecord(
            strategy_name=strategy_name,
            from_stage=current.value,
            to_stage=new_stage.value,
            timestamp=now,
            criteria_met=criteria_met,
        )

    def all_stages(self) -> dict[str, str]:
        """Return current stage for all tracked strategies."""
        with self._lock:
            rows = self._conn.execute(
                "SELECT strategy_name, stage FROM promotions"
            ).fetchall()
        return {r[0]: r[1] for r in rows}

    def history(self, strategy_name: str | None = None, limit: int = 50) -> list[PromotionRecord]:
        """Return promotion history, optionally filtered by strategy."""
        import json
        with self._lock:
            if strategy_name:
                rows = self._conn.execute(
                    """SELECT strategy_name, from_stage, to_stage, timestamp, criteria_json
                       FROM promotion_history WHERE strategy_name = ?
                       ORDER BY timestamp DESC LIMIT ?""",
                    (strategy_name, limit),
                ).fetchall()
            else:
                rows = self._conn.execute(
                    """SELECT strategy_name, from_stage, to_stage, timestamp, criteria_json
                       FROM promotion_history ORDER BY timestamp DESC LIMIT ?""",
                    (limit,),
                ).fetchall()

        return [
            PromotionRecord(
                strategy_name=r[0],
                from_stage=r[1],
                to_stage=r[2],
                timestamp=r[3],
                criteria_met=json.loads(r[4]) if r[4] else {},
            )
            for r in reversed(rows)
        ]

    def close(self) -> None:
        """Close the database connection."""
        with self._lock:
            self._conn.close()
