#!/usr/bin/env python3
"""Strategy Review Board — human-in-the-loop approval workflow.

Every AI-generated or sandbox-promoted strategy must pass review before
advancing to paper trading.  This module manages the review queue, tracks
decisions, and enforces the approval gates.

Usage::

    from strategy_review_board import StrategyReviewBoard

    board = StrategyReviewBoard()
    board.submit("my_strategy", source="ai_generated", code="...")
    board.approve("my_strategy", reviewer="trader_1", notes="Looks solid")
"""

from __future__ import annotations

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

import config_manager


@dataclass
class ReviewEntry:
    strategy_id: str
    source: str  # ai_generated, sandbox_promotion, manual
    status: str = "pending"  # pending, approved, rejected, needs_revision
    submitted_at: str = ""
    reviewer: str = ""
    reviewed_at: str = ""
    notes: str = ""
    sandbox_result: dict[str, Any] | None = None
    revisions: list[dict[str, Any]] = field(default_factory=list)


class StrategyReviewBoard:
    """Approval workflow manager for strategy promotion.

    Gates:
      1. AI-generated code must pass sandbox → submit for review
      2. Human reviewer approves/rejects/requests revision
      3. Approved strategies advance to paper trading
      4. Rejected strategies are archived with reason
    """

    def __init__(self):
        self._queue: dict[str, ReviewEntry] = {}
        self._load()

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

    def _review_file(self) -> Path:
        return config_manager.get_logs_dir() / "strategy_review_board.json"

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

    def _load(self) -> None:
        path = self._review_file()
        if not path.exists():
            return
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
            for sid, entry in data.get("queue", {}).items():
                self._queue[sid] = ReviewEntry(**entry)
        except Exception:
            pass

    def _save(self) -> None:
        path = self._review_file()
        path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "updated_at": datetime.now(timezone.utc).isoformat(),
            "queue": {
                sid: {
                    "strategy_id": e.strategy_id,
                    "source": e.source,
                    "status": e.status,
                    "submitted_at": e.submitted_at,
                    "reviewer": e.reviewer,
                    "reviewed_at": e.reviewed_at,
                    "notes": e.notes,
                    "sandbox_result": e.sandbox_result,
                    "revisions": e.revisions,
                }
                for sid, e in self._queue.items()
            },
        }
        path.write_text(
            json.dumps(payload, indent=2, ensure_ascii=False, default=str),
            encoding="utf-8",
        )

    # -- submission -------------------------------------------------------

    def submit(
        self,
        strategy_id: str,
        source: str = "ai_generated",
        code: str = "",
        sandbox_result: dict[str, Any] | None = None,
    ) -> ReviewEntry:
        """Submit a strategy for review.

        Returns the ReviewEntry (status='pending').
        """
        entry = ReviewEntry(
            strategy_id=strategy_id,
            source=source,
            status="pending",
            submitted_at=datetime.now(timezone.utc).isoformat(),
            sandbox_result=sandbox_result,
        )
        self._queue[strategy_id] = entry
        self._save()
        return entry

    # -- decision ---------------------------------------------------------

    def approve(self, strategy_id: str, reviewer: str = "", notes: str = "") -> ReviewEntry:
        """Approve a strategy for paper trading."""
        entry = self._get_or_raise(strategy_id)
        entry.status = "approved"
        entry.reviewer = reviewer
        entry.reviewed_at = datetime.now(timezone.utc).isoformat()
        entry.notes = notes or entry.notes
        self._save()
        self._promote_manifest_trust(strategy_id, "paper")
        return entry

    def reject(self, strategy_id: str, reviewer: str = "", reason: str = "") -> ReviewEntry:
        """Reject a strategy with a reason."""
        entry = self._get_or_raise(strategy_id)
        entry.status = "rejected"
        entry.reviewer = reviewer
        entry.reviewed_at = datetime.now(timezone.utc).isoformat()
        entry.notes = reason or entry.notes
        self._save()
        return entry

    def request_revision(
        self, strategy_id: str, reviewer: str = "", feedback: str = ""
    ) -> ReviewEntry:
        """Request changes to a strategy."""
        entry = self._get_or_raise(strategy_id)
        entry.status = "needs_revision"
        entry.reviewer = reviewer
        entry.reviewed_at = datetime.now(timezone.utc).isoformat()
        entry.notes = feedback or entry.notes
        entry.revisions.append(
            {
                "requested_at": datetime.now(timezone.utc).isoformat(),
                "reviewer": reviewer,
                "feedback": feedback,
            }
        )
        self._save()
        return entry

    def reopen_pending(
        self,
        strategy_id: str,
        reviewer: str = "",
        notes: str = "",
    ) -> ReviewEntry:
        """Move a reviewed strategy back to pending review."""
        entry = self._get_or_raise(strategy_id)
        entry.status = "pending"
        entry.reviewer = reviewer
        entry.reviewed_at = datetime.now(timezone.utc).isoformat()
        entry.notes = notes or entry.notes
        self._save()
        return entry

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

    def get(self, strategy_id: str) -> ReviewEntry | None:
        return self._queue.get(strategy_id)

    def list_pending(self) -> list[ReviewEntry]:
        return [e for e in self._queue.values() if e.status == "pending"]

    def list_all(self) -> list[ReviewEntry]:
        return sorted(
            self._queue.values(),
            key=lambda e: e.submitted_at,
            reverse=True,
        )

    def list_approved(self) -> list[ReviewEntry]:
        return [e for e in self._queue.values() if e.status == "approved"]

    def is_approved(self, strategy_id: str) -> bool:
        entry = self._queue.get(strategy_id)
        return entry is not None and entry.status == "approved"

    def delete(self, strategy_id: str) -> bool:
        if strategy_id in self._queue:
            del self._queue[strategy_id]
            self._save()
            return True
        return False

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

    def _get_or_raise(self, strategy_id: str) -> ReviewEntry:
        entry = self._queue.get(strategy_id)
        if entry is None:
            raise KeyError(f"Strategy not in review queue: {strategy_id}")
        return entry

    def _promote_manifest_trust(self, strategy_id: str, new_level: str) -> None:
        """Update the manifest trust_level after approval."""
        manifest_path = (
            config_manager.get_project_dir()
            / "strategies"
            / 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"] = new_level
            manifest_path.write_text(
                json.dumps(manifest, indent=2, ensure_ascii=False),
                encoding="utf-8",
            )
        except Exception:
            pass
