#!/usr/bin/env python3
"""Autonomous Retirement — graceful strategy decommissioning.

When a strategy proves itself unfit for live trading over an extended
period, this module downgrades its trust level progressively and
eventually retires it.  Every action is logged for audit.

Trust level downgrade path:
    proven → live_full → live_reduced → paper → retired

Usage::

    from autonomous_retirement import AutonomousRetirement

    retire = AutonomousRetirement()
    rec = retire.evaluate("my_strategy")
    if rec["should_retire"]:
        retire.retire("my_strategy", rec["reason"])
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import config_manager

SRC_DIR = Path(__file__).resolve().parent
PROJECT = SRC_DIR.parent

_DOWNGRADE_PATH = [
    "proven",
    "live_full",
    "live_reduced",
    "paper",
    "retired",
]


@dataclass
class RetirementConfig:
    """Thresholds that trigger retirement evaluation."""

    min_live_trades: int = 30
    max_drawdown_pct: float = 40.0
    min_win_rate_pct: float = 30.0
    max_consecutive_losses: int = 8
    max_days_since_last_win: int = 14
    arena_bottom_streak: int = 3
    min_recovery_bars: int = 500  # bars in paper before re-promotion considered


class AutonomousRetirement:
    """Graceful, audited strategy retirement engine.

    Evaluates strategies against configurable criteria and executes
    progressive downgrades:

    1. Evaluate → produce a recommendation with supporting evidence.
    2. Downgrade → move trust_level one step down the hierarchy.
    3. Retire → final step: set trust_level to "retired", log reason.
    """

    def __init__(self, config: RetirementConfig | None = None):
        self._config = config or RetirementConfig()
        self._retired: dict[str, dict[str, Any]] = {}
        self._downgrades: list[dict[str, Any]] = []
        self._load()

    # -- paths -----------------------------------------------------------

    def _retired_path(self) -> Path:
        return config_manager.get_logs_dir() / "retired_strategies.json"

    def _downgrade_log_path(self) -> Path:
        return config_manager.get_logs_dir() / "retirement_downgrades.ndjson"

    def _manifest_dir(self) -> Path:
        return config_manager.get_project_dir() / "strategies"

    # -- persistence -----------------------------------------------------

    def _load(self) -> None:
        path = self._retired_path()
        if path.exists():
            try:
                self._retired = json.loads(path.read_text(encoding="utf-8"))
            except Exception:
                pass

    def _save_retired(self) -> None:
        path = self._retired_path()
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(
            json.dumps(self._retired, indent=2, ensure_ascii=False, default=str),
            encoding="utf-8",
        )

    def _log_downgrade(self, entry: dict[str, Any]) -> None:
        path = self._downgrade_log_path()
        path.parent.mkdir(parents=True, exist_ok=True)
        entry["logged_at"] = datetime.now(timezone.utc).isoformat()
        self._downgrades.append(entry)
        with open(path, "a", encoding="utf-8") as f:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")

    # -- evaluation ------------------------------------------------------

    def evaluate(self, strategy_id: str) -> dict[str, Any]:
        """Evaluate a strategy for retirement candidacy.

        Returns a dict with ``should_retire``, ``should_downgrade``,
        ``reasons``, and supporting evidence.
        """
        evidence = self._gather_evidence(strategy_id)
        reasons: list[str] = []

        cfg = self._config

        trades = evidence["live_trades"]
        if len(trades) >= cfg.min_live_trades:
            wr = evidence["win_rate"]
            if wr < cfg.min_win_rate_pct:
                reasons.append(
                    f"win rate {wr:.1f}% below {cfg.min_win_rate_pct}% "
                    f"over {len(trades)} live trades"
                )

            dd = evidence["max_drawdown_pct"]
            if dd > cfg.max_drawdown_pct:
                reasons.append(
                    f"max drawdown {dd:.1f}% exceeds {cfg.max_drawdown_pct}%"
                )

            cl = evidence["consecutive_losses"]
            if cl >= cfg.max_consecutive_losses:
                reasons.append(
                    f"{cl} consecutive losses (threshold: {cfg.max_consecutive_losses})"
                )

        days_since_win = evidence.get("days_since_last_win", 0)
        if days_since_win > cfg.max_days_since_last_win and evidence.get("total_trades", 0) > 10:
            reasons.append(
                f"{days_since_win} days since last winning trade"
            )

        arena_streak = evidence.get("arena_bottom_streak", 0)
        if arena_streak >= cfg.arena_bottom_streak:
            reasons.append(
                f"arena bottom streak {arena_streak} >= {cfg.arena_bottom_streak}"
            )

        current_level = evidence["current_trust_level"]
        should_retire = current_level == "paper" and len(reasons) >= 2
        should_downgrade = current_level not in ("retired",) and len(reasons) > 0 and not should_retire

        return {
            "strategy_id": strategy_id,
            "current_trust_level": current_level,
            "should_retire": should_retire,
            "should_downgrade": should_downgrade,
            "reasons": reasons,
            "evidence": {
                "total_trades": len(trades),
                "win_rate": evidence["win_rate"],
                "max_drawdown_pct": evidence["max_drawdown_pct"],
                "consecutive_losses": evidence["consecutive_losses"],
                "days_since_last_win": evidence["days_since_last_win"],
                "arena_bottom_streak": evidence["arena_bottom_streak"],
            },
            "evaluated_at": datetime.now(timezone.utc).isoformat(),
        }

    def _gather_evidence(self, strategy_id: str) -> dict[str, Any]:
        from src.pnl_feedback_scorer import PnLFeedbackScorer

        scorer = PnLFeedbackScorer(min_trades_for_score=1)
        live_data = scorer.score_strategy(strategy_id)

        ledger = self._load_ledger_trades(strategy_id, mode="live")
        paper_ledger = self._load_ledger_trades(strategy_id, mode="paper")

        all_trades = sorted(
            ledger + paper_ledger,
            key=lambda t: str(t.get("timestamp", "")),
        )

        wr = (
            sum(1 for t in ledger if float(t.get("pnl", 0) or 0) > 0)
            / max(len(ledger), 1)
            * 100
        )

        equity = 0.0
        peak = 0.0
        max_dd = 0.0
        for t in ledger:
            equity += float(t.get("pnl", 0) or 0)
            peak = max(peak, equity)
            if peak > 0:
                max_dd = max(max_dd, (peak - equity) / peak * 100)

        # Sort by timestamp descending (most recent first)
        sorted_ledger = sorted(
            ledger,
            key=lambda t: str(t.get("timestamp", "")),
            reverse=True,
        )

        consecutive = 0
        for t in sorted_ledger:
            if float(t.get("pnl", 0) or 0) <= 0:
                consecutive += 1
            else:
                break

        days_since_win = 0
        for t in sorted_ledger:
            if float(t.get("pnl", 0) or 0) > 0:
                break
        else:
            if sorted_ledger:
                ts = str(sorted_ledger[-1].get("timestamp", ""))
                if ts:
                    try:
                        last = datetime.fromisoformat(ts.replace("Z", "+00:00"))
                        days_since_win = int(
                            (datetime.now(timezone.utc) - last).total_seconds()
                            / 86400
                        )
                    except ValueError:
                        pass

        current_level = self._read_manifest_trust_level(strategy_id)

        arena_streak = 0
        arena_path = config_manager.get_logs_dir() / "arena_state.json"
        if arena_path.exists():
            try:
                arena = json.loads(arena_path.read_text(encoding="utf-8"))
                standing = arena.get("standings", {}).get(strategy_id, {})
                arena_streak = standing.get("bottom_streak", 0)
            except Exception:
                pass

        return {
            "live_trades": ledger,
            "paper_trades": paper_ledger,
            "all_trades": all_trades,
            "total_trades": len(ledger),
            "win_rate": wr,
            "max_drawdown_pct": round(max_dd, 2),
            "consecutive_losses": consecutive,
            "days_since_last_win": days_since_win,
            "current_trust_level": current_level,
            "arena_bottom_streak": arena_streak,
            "live_score": live_data.get("live_score", 0.0),
        }

    # -- actions ---------------------------------------------------------

    def downgrade(
        self, strategy_id: str, reason: str = ""
    ) -> dict[str, Any]:
        """Move strategy one step down the trust hierarchy."""
        current = self._read_manifest_trust_level(strategy_id)
        if current == "retired":
            return {
                "strategy_id": strategy_id,
                "action": "none",
                "reason": "already retired",
                "new_level": "retired",
            }

        try:
            idx = _DOWNGRADE_PATH.index(current)
            new_level = _DOWNGRADE_PATH[min(idx + 1, len(_DOWNGRADE_PATH) - 1)]
        except ValueError:
            new_level = "paper"

        self._write_manifest_trust_level(strategy_id, new_level)

        entry = {
            "strategy_id": strategy_id,
            "action": "downgrade" if new_level != "retired" else "retire",
            "from_level": current,
            "to_level": new_level,
            "reason": reason,
        }
        self._log_downgrade(entry)

        if new_level == "retired":
            self._retired[strategy_id] = {
                "strategy_id": strategy_id,
                "retired_at": datetime.now(timezone.utc).isoformat(),
                "final_level": new_level,
                "reason": reason,
            }
            self._save_retired()

        return {
            "strategy_id": strategy_id,
            "action": "downgrade" if new_level != "retired" else "retire",
            "from_level": current,
            "new_level": new_level,
            "reason": reason,
        }

    def retire(self, strategy_id: str, reason: str = "") -> dict[str, Any]:
        """Immediately retire a strategy regardless of current level."""
        current = self._read_manifest_trust_level(strategy_id)
        self._write_manifest_trust_level(strategy_id, "retired")

        entry = {
            "strategy_id": strategy_id,
            "action": "retire",
            "from_level": current,
            "to_level": "retired",
            "reason": reason,
        }
        self._log_downgrade(entry)

        self._retired[strategy_id] = {
            "strategy_id": strategy_id,
            "retired_at": datetime.now(timezone.utc).isoformat(),
            "final_level": "retired",
            "reason": reason,
        }
        self._save_retired()

        return {
            "strategy_id": strategy_id,
            "action": "retire",
            "from_level": current,
            "new_level": "retired",
            "reason": reason,
        }

    # -- query -----------------------------------------------------------

    def is_retired(self, strategy_id: str) -> bool:
        return strategy_id in self._retired

    def get_retired(self) -> dict[str, dict[str, Any]]:
        return dict(self._retired)

    def get_downgrade_history(
        self, strategy_id: str
    ) -> list[dict[str, Any]]:
        return [d for d in self._downgrades if d["strategy_id"] == strategy_id]

    # -- helpers ---------------------------------------------------------

    def _read_manifest_trust_level(self, strategy_id: str) -> str:
        manifest_path = self._manifest_dir() / f"{strategy_id}.manifest.json"
        if not manifest_path.exists():
            return "builtin"
        try:
            manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
            return str(manifest.get("trust_level", "sandbox"))
        except Exception:
            return "sandbox"

    def _write_manifest_trust_level(self, strategy_id: str, level: str) -> None:
        manifest_path = self._manifest_dir() / f"{strategy_id}.manifest.json"
        if not manifest_path.exists():
            return
        try:
            manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
            manifest["trust_level"] = level
            manifest["updated_at"] = datetime.now(timezone.utc).isoformat()
            manifest_path.write_text(
                json.dumps(manifest, indent=2, ensure_ascii=False),
                encoding="utf-8",
            )
        except Exception:
            pass

    @staticmethod
    def _load_ledger_trades(
        strategy_id: str, mode: str
    ) -> list[dict[str, Any]]:
        path = config_manager.get_logs_dir() / "trading_ledger.ndjson"
        if not path.exists():
            return []
        trades: list[dict[str, Any]] = []
        try:
            for line in path.read_text(encoding="utf-8").strip().splitlines():
                if not line.strip():
                    continue
                entry = json.loads(line)
                sid = str(entry.get("strategy_id", "") or "")
                m = str(entry.get("mode", "") or entry.get("trade_type", "") or "")
                if sid == strategy_id and m == mode:
                    trades.append(entry)
        except Exception:
            pass
        return trades
