"""
autonomy_controller.py — Decides auto-approve vs flag-for-human.

Phase 1: disabled (all shots go to human review).
Phase 2: conservative thresholds.
Phase 3: widen based on learned patterns from LearningEngine.

The controller never writes to the store — it returns a decision that the
production loop acts on. This keeps the decision surface testable.
"""

import logging
from typing import Optional

from orchestrator.production_types import AutonomyConfig

logger = logging.getLogger(__name__)


class AutonomyController:
    """Evaluates whether a passed shot can be auto-approved.

    Thread-safe: no mutable shared state. Config is frozen.
    """

    def __init__(self, config: AutonomyConfig, initial_count: int = 0):
        self._config = config
        self._auto_approved_count = initial_count

    @property
    def config(self) -> AutonomyConfig:
        return self._config

    def can_auto_approve(
        self,
        shot_data: dict,
        gate_results: dict,
        deferred: bool = False,
        current_auto_approved: Optional[int] = None,
    ) -> bool:
        """Decide if a shot can skip human review.

        Args:
            shot_data: Full shot record from ExecutionStore.
            gate_results: Merged gate results dict from the shot.
            deferred: Whether any gate returned DEFERRED (e.g. Gate 3).
            current_auto_approved: If provided, overrides internal counter
                (useful for crash recovery where the internal counter reset).

        Returns:
            True if the shot can be auto-approved, False if human review needed.
        """
        if not self._config.enabled:
            return False

        # Budget cap on auto-approvals per batch
        count = current_auto_approved if current_auto_approved is not None else self._auto_approved_count
        if (self._config.max_auto_approve_per_batch > 0
                and count >= self._config.max_auto_approve_per_batch):
            logger.info(
                "Auto-approve cap reached (%d/%d)",
                count,
                self._config.max_auto_approve_per_batch,
            )
            return False

        # Deferred shots always need human review
        if deferred:
            logger.info("Shot %s: deferred → needs review", shot_data.get("shot_id", "?"))
            return False

        # Gate 3 requirement
        if self._config.require_gate_3:
            g3 = gate_results.get("gate_3", {})
            if not g3 or g3.get("deferred", False):
                return False

        # All gates must pass
        if self._config.require_all_gates_pass:
            for gate_name, result in gate_results.items():
                if gate_name.startswith("gate_") and isinstance(result, dict):
                    if not result.get("passed", False):
                        return False

        # Exclude complex shot types
        pipeline = shot_data.get("pipeline", "")
        if pipeline in self._config.exclude_shot_types:
            return False

        return True

    def record_approval(self) -> None:
        """Called by the production loop after auto-approving a shot."""
        self._auto_approved_count += 1

    def reset_counts(self, initial_count: int = 0) -> None:
        """Reset per-batch counters. Called at batch start.

        Args:
            initial_count: Starting count for auto-approved, used for crash
                recovery from BatchState.auto_approved.
        """
        self._auto_approved_count = initial_count
