"""Autonomous agent: the top-level LLM interface for fund operation."""

from __future__ import annotations

import logging
import threading
import time
from typing import Any

from horizon.fund._decisions import (
    ActionType,
    ConfidenceThresholds,
    DecisionFramework,
    OperatingMode,
    RateLimits,
)
from horizon.fund._types import FundConfig, StrategyConfig

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


class AutonomousAgent:
    """The LLM-facing interface for autonomous fund operation.

    Wraps FundManager with the DecisionFramework so every action
    passes through confidence checks, rate limits, and audit logging.

    Modes:
    - dry_run: LLM proposes, human approves each action
    - supervised: LLM executes low-risk, escalates high-risk
    - autonomous: LLM executes all within guardrails

    Usage::

        agent = AutonomousAgent(fund, mode="supervised")

        # LLM proposes deploying a strategy
        decision = agent.deploy_strategy(
            config=StrategyConfig(...),
            reasoning="High fitness score, backtest Sharpe 1.8",
            confidence=0.75,
        )

        # In dry-run mode, human approves
        agent.approve(decision.decision_id)
    """

    def __init__(
        self,
        fund: Any,  # FundManager
        mode: str | OperatingMode = OperatingMode.SUPERVISED,
        thresholds: ConfidenceThresholds | None = None,
        rate_limits: RateLimits | None = None,
        db_path: str | None = None,
    ) -> None:
        self._fund = fund
        if isinstance(mode, str):
            mode = OperatingMode(mode)
        self._decisions = DecisionFramework(
            mode=mode,
            thresholds=thresholds,
            rate_limits=rate_limits,
            db_path=db_path,
        )
        self._lock = threading.Lock()
        self._pending_configs: dict[str, StrategyConfig] = {}  # decision_id -> config

    @property
    def fund(self) -> Any:
        return self._fund

    @property
    def mode(self) -> OperatingMode:
        return self._decisions.mode

    def set_mode(self, mode: str | OperatingMode) -> None:
        if isinstance(mode, str):
            mode = OperatingMode(mode)
        self._decisions.set_mode(mode)

    # ------------------------------------------------------------------
    # Actions (each goes through decision framework)
    # ------------------------------------------------------------------

    def deploy_strategy(
        self,
        config: StrategyConfig,
        reasoning: str,
        confidence: float,
        risk_config: Any = None,
        backtest_sharpe: float = 0.0,
        n_trades: int = 0,
        pbo: float | None = None,
        deflated_sharpe: float | None = None,
        oos_sharpe: float | None = None,
        n_trials: int = 1,
    ) -> Any:
        """Deploy a strategy (goes through decision framework + bias guard).

        Returns the Decision object. If outcome is 'executed',
        the strategy has been deployed.
        """
        # Bias guard pre-check: screen for overfitting before decision framework
        # Only runs when backtest metrics are provided (n_trades > 0)
        from horizon.fund._bias_guard import BiasGuard
        bias_guard = getattr(self._fund, "bias_guard", None)
        if isinstance(bias_guard, BiasGuard) and n_trades > 0:
            strategy_type = config.pipeline[0].__name__ if config.pipeline else "unknown"
            market_id = config.markets[0] if config.markets else "unknown"
            alerts = bias_guard.check_deploy(
                strategy_type=strategy_type,
                market_id=market_id,
                backtest_sharpe=backtest_sharpe,
                n_trades=n_trades,
                pbo=pbo,
                deflated_sharpe=deflated_sharpe,
                oos_sharpe=oos_sharpe,
                n_trials=n_trials,
            )
            if bias_guard.should_block(alerts):
                block_reasons = [a.message for a in alerts if a.severity == "block"]
                from horizon.fund._decisions import Decision
                return Decision(
                    decision_id="bias_blocked",
                    timestamp=time.time(),
                    action=ActionType.DEPLOY_STAGING if config.mode == "paper" else ActionType.PROMOTE_LIVE,
                    parameters={"name": config.name},
                    reasoning=reasoning,
                    confidence=confidence,
                    outcome="rejected",
                    error=f"Bias guard blocked: {'; '.join(block_reasons)}",
                )

        action = ActionType.DEPLOY_STAGING if config.mode == "paper" else ActionType.PROMOTE_LIVE
        decision = self._decisions.propose(
            action=action,
            parameters={
                "name": config.name,
                "markets": config.markets,
                "mode": config.mode,
                "strategy_type": config.pipeline[0].__name__ if config.pipeline else "unknown",
            },
            reasoning=reasoning,
            confidence=confidence,
        )

        if decision.outcome == "executed":
            try:
                self._fund.add_strategy(config, risk_config)
            except Exception as e:
                decision.outcome = "failed"
                decision.error = str(e)
                logger.error("Deploy failed for %s: %s", config.name, e)
        elif decision.outcome in ("pending", "escalated"):
            # Store the full config for later execution on approve
            self._pending_configs[decision.decision_id] = config

        return decision

    def retire_strategy(
        self,
        name: str,
        reasoning: str,
        confidence: float,
    ) -> Any:
        """Retire a strategy."""
        decision = self._decisions.propose(
            action=ActionType.RETIRE_STRATEGY,
            parameters={"name": name},
            reasoning=reasoning,
            confidence=confidence,
        )

        if decision.outcome == "executed":
            try:
                self._fund.remove_strategy(name)
            except Exception as e:
                decision.outcome = "failed"
                decision.error = str(e)

        return decision

    def scale_strategy(
        self,
        name: str,
        new_capital: float,
        reasoning: str,
        confidence: float,
        daily_returns: list[float] | None = None,
        age_days: float = 0.0,
    ) -> Any:
        """Scale a strategy's capital allocation (with bias guard check for scale-up)."""
        current = 0.0
        try:
            status = self._fund.controller.status(name)
            current = status.allocated_capital
        except Exception:
            pass

        # Bias guard: check for performance chasing on scale-up
        if new_capital > current:
            from horizon.fund._bias_guard import BiasGuard
            bias_guard = getattr(self._fund, "bias_guard", None)
            if isinstance(bias_guard, BiasGuard) and daily_returns is not None:
                alerts = bias_guard.check_scale_up(
                    strategy_name=name,
                    daily_returns=daily_returns,
                    age_days=age_days,
                )
                if bias_guard.should_block(alerts):
                    block_reasons = [a.message for a in alerts if a.severity == "block"]
                    from horizon.fund._decisions import Decision
                    return Decision(
                        decision_id="bias_blocked",
                        timestamp=time.time(),
                        action=ActionType.SCALE_UP,
                        parameters={"name": name, "new_capital": new_capital},
                        reasoning=reasoning,
                        confidence=confidence,
                        outcome="rejected",
                        error=f"Bias guard blocked scale-up: {'; '.join(block_reasons)}",
                    )

        action = ActionType.SCALE_UP if new_capital > current else ActionType.SCALE_DOWN
        decision = self._decisions.propose(
            action=action,
            parameters={"name": name, "current_capital": current, "new_capital": new_capital},
            reasoning=reasoning,
            confidence=confidence,
        )

        if decision.outcome == "executed":
            try:
                self._fund.scale_strategy(name, new_capital)
            except Exception as e:
                decision.outcome = "failed"
                decision.error = str(e)

        return decision

    def activate_kill_switch(
        self,
        reasoning: str,
        confidence: float,
    ) -> Any:
        """Activate the fund kill switch."""
        decision = self._decisions.propose(
            action=ActionType.KILL_SWITCH,
            parameters={},
            reasoning=reasoning,
            confidence=confidence,
        )

        if decision.outcome == "executed":
            try:
                self._fund.risk_monitor._fund_kill_switch = True
                self._fund.controller.stop_all()
                logger.critical("KILL SWITCH activated by agent: %s", reasoning)
            except Exception as e:
                decision.outcome = "failed"
                decision.error = str(e)

        return decision

    def rebalance(
        self,
        reasoning: str,
        confidence: float,
    ) -> Any:
        """Trigger a capital rebalance."""
        decision = self._decisions.propose(
            action=ActionType.REBALANCE,
            parameters={},
            reasoning=reasoning,
            confidence=confidence,
        )

        if decision.outcome == "executed":
            try:
                engines = self._fund.controller.engines
                names = list(engines.keys())
                allocations = self._fund.allocator.allocate(names, engines)
                for name, capital in allocations.items():
                    self._fund.scale_strategy(name, capital)
            except Exception as e:
                decision.outcome = "failed"
                decision.error = str(e)

        return decision

    def research_scan(
        self,
        reasoning: str = "Periodic market scan",
        confidence: float = 0.8,
    ) -> Any:
        """Trigger a research scan."""
        decision = self._decisions.propose(
            action=ActionType.RESEARCH_SCAN,
            parameters={},
            reasoning=reasoning,
            confidence=confidence,
        )
        # Research scan itself is executed by the caller with the pipeline
        return decision

    # ------------------------------------------------------------------
    # Human-in-the-loop
    # ------------------------------------------------------------------

    def approve(self, decision_id: str) -> bool:
        """Approve a pending decision."""
        approved = self._decisions.approve(decision_id)
        if approved:
            # Retrieve the approved decision by ID from the audit log
            d = self._decisions.get_decision(decision_id)
            if d is not None:
                self._execute_approved(d)
        return approved

    def reject(self, decision_id: str, reason: str = "") -> bool:
        """Reject a pending decision."""
        self._pending_configs.pop(decision_id, None)
        return self._decisions.reject(decision_id, reason)

    def pending(self) -> list[Any]:
        """Return pending decisions awaiting approval."""
        return self._decisions.pending_decisions()

    # ------------------------------------------------------------------
    # Status and audit
    # ------------------------------------------------------------------

    def decision_log(self, limit: int = 50) -> list[Any]:
        """Return recent decisions."""
        return self._decisions.recent_decisions(limit)

    def stats(self) -> dict[str, Any]:
        """Return agent statistics."""
        fund_status = self._fund.status()
        decision_stats = self._decisions.stats()
        return {
            "mode": self.mode.value,
            "fund": fund_status,
            "decisions": decision_stats,
        }

    def close(self) -> None:
        self._decisions.close()

    # ------------------------------------------------------------------
    # Internal
    # ------------------------------------------------------------------

    def _execute_approved(self, decision: Any) -> None:
        """Execute a previously approved decision."""
        try:
            if decision.action in (ActionType.DEPLOY_STAGING, ActionType.PROMOTE_LIVE):
                # Use the stored full config if available, otherwise fallback
                config = self._pending_configs.pop(decision.decision_id, None)
                if config is None:
                    name = decision.parameters.get("name", "")
                    markets = decision.parameters.get("markets", [])
                    mode = "paper" if decision.action == ActionType.DEPLOY_STAGING else "live"
                    config = StrategyConfig(
                        name=name,
                        pipeline=[],
                        markets=markets,
                        mode=mode,
                    )
                self._fund.add_strategy(config)
            elif decision.action == ActionType.RETIRE_STRATEGY:
                self._fund.remove_strategy(decision.parameters.get("name", ""))
            elif decision.action == ActionType.KILL_SWITCH:
                self._fund.risk_monitor._fund_kill_switch = True
                self._fund.controller.stop_all()
            elif decision.action in (ActionType.SCALE_UP, ActionType.SCALE_DOWN):
                name = decision.parameters.get("name", "")
                new_capital = decision.parameters.get("new_capital", 0.0)
                if name and new_capital > 0:
                    self._fund.scale_strategy(name, new_capital)
            elif decision.action == ActionType.REBALANCE:
                engines = self._fund.controller.engines
                names = list(engines.keys())
                allocations = self._fund.allocator.allocate(names, engines)
                for name, capital in allocations.items():
                    self._fund.scale_strategy(name, capital)
        except Exception as e:
            logger.error("Error executing approved decision %s: %s", decision.decision_id, e)
