"""AD-381: InitiativeEngine — proactive remediation from system health signals."""

from __future__ import annotations

import asyncio
import logging
import time
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable

from probos.events import EventType

logger = logging.getLogger(__name__)


class TriggerSource(str, Enum):
    SIF = "sif"
    EMERGENT = "emergent"
    COUNSELOR = "counselor"


class ActionType(str, Enum):
    DIAGNOSE = "diagnose"      # run diagnostics — low risk
    SCALE = "scale"            # adjust pool size — low risk
    RECYCLE = "recycle"        # restart degraded agent — medium risk
    PATCH = "patch"            # trigger self-mod pipeline — high risk
    ALERT_CAPTAIN = "alert_captain"  # escalate to human — always allowed


class ActionGate(str, Enum):
    """Trust tier required to auto-execute an action."""
    AUTO = "auto"              # always executes (diagnose, alert_captain)
    COMMANDER = "commander"    # auto if system trust > 0.7 (scale, recycle)
    CAPTAIN = "captain"        # always requires Captain approval (patch)


@dataclass
class RemediationProposal:
    """A proposed remediation action generated by the InitiativeEngine."""
    id: str  # uuid
    trigger_source: TriggerSource
    trigger_detail: str  # human-readable description of what triggered this
    action_type: ActionType
    action_detail: str  # what specifically should be done
    gate: ActionGate
    severity: str  # "low", "medium", "high"
    affected_agents: list[str] = field(default_factory=list)
    persistence_count: int = 1  # how many consecutive checks this trigger has persisted
    created_at: float = field(default_factory=time.time)
    status: str = "proposed"  # proposed, approved, rejected, executed

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": self.id,
            "trigger_source": self.trigger_source.value,
            "trigger_detail": self.trigger_detail,
            "action_type": self.action_type.value,
            "action_detail": self.action_detail,
            "gate": self.gate.value,
            "severity": self.severity,
            "affected_agents": self.affected_agents,
            "persistence_count": self.persistence_count,
            "created_at": self.created_at,
            "status": self.status,
        }


@dataclass
class TriggerState:
    """Tracks a persistent trigger across consecutive check cycles."""
    source: TriggerSource
    key: str  # unique identifier for this specific trigger (e.g., "sif:trust_bounds")
    detail: str
    first_seen: float = field(default_factory=time.time)
    consecutive_count: int = 1
    last_seen: float = field(default_factory=time.time)


class InitiativeEngine:
    """Monitors system health signals and proposes remediation actions.

    The engine runs a periodic check loop. When a trigger persists for
    `persistence_threshold` consecutive checks, it generates a RemediationProposal.

    Trust-gated execution:
    - AUTO actions (diagnose, alert_captain): always execute
    - COMMANDER actions (scale, recycle): auto-execute if system is healthy
    - CAPTAIN actions (patch): always require human approval via on_proposal callback

    Fails-open: if any check raises an exception, log and continue.
    """

    def __init__(
        self,
        *,
        check_interval: float = 30.0,  # seconds between checks
        persistence_threshold: int = 3,  # consecutive occurrences before proposing
        on_proposal: Callable[[RemediationProposal], Any] | None = None,
        on_event: Callable[[dict], Any] | None = None,
    ) -> None:
        self._check_interval = check_interval
        self._persistence_threshold = persistence_threshold
        self._on_proposal = on_proposal
        self._on_event = on_event
        self._triggers: dict[str, TriggerState] = {}
        self._proposals: list[RemediationProposal] = []
        self._task: asyncio.Task | None = None
        # External references set via set_*() after construction
        self._sif: Any = None           # StructuralIntegrityField
        self._detector: Any = None      # EmergentDetector
        self._counselor_fn: Callable | None = None  # Callable returning list[CounselorAssessment]

    def set_sif(self, sif: Any) -> None:
        """Wire the SIF reference for invariant checks."""
        self._sif = sif

    def set_detector(self, detector: Any) -> None:
        """Wire the EmergentDetector for trend checks."""
        self._detector = detector

    def set_counselor_fn(self, fn: Callable) -> None:
        """Wire a callable that returns recent counselor assessments."""
        self._counselor_fn = fn

    async def start(self) -> None:
        """Start the periodic check loop."""
        self._task = asyncio.create_task(self._check_loop())

    async def stop(self) -> None:
        """Stop the check loop."""
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass
            self._task = None

    async def _check_loop(self) -> None:
        """Main loop: check signals, update triggers, generate proposals."""
        while True:
            try:
                await self._run_checks()
            except asyncio.CancelledError:
                raise
            except Exception:
                logger.exception("InitiativeEngine check failed (fail-open)")
            await asyncio.sleep(self._check_interval)

    async def _run_checks(self) -> None:
        """Run all signal checks and update trigger states.

        For each signal source, collect current violations/alerts.
        For each violation:
        - If already tracked in _triggers: increment consecutive_count
        - If new: create TriggerState with count=1
        Clear triggers that are no longer active (problem resolved).
        For triggers that reach persistence_threshold: generate proposal.
        """
        active_keys: set[str] = set()

        # 1. SIF violations
        if self._sif:
            try:
                report = self._sif.last_report
                if report:
                    for check in report.checks:
                        if not check.passed:
                            key = f"sif:{check.name}"
                            active_keys.add(key)
                            self._update_trigger(TriggerSource.SIF, key, check.details or check.name)
            except Exception:
                logger.debug("SIF check skipped", exc_info=True)

        # 2. EmergentDetector — check for significant falling trends
        #    (rising trends are good, falling cooperation is concerning)
        if self._detector:
            try:
                trend_report = self._detector.compute_trends()
                if trend_report:
                    for trend in trend_report.significant_trends:
                        if trend.direction.value == "falling" and trend.metric_name in ("tc_n", "capability_count"):
                            key = f"emergent:{trend.metric_name}_falling"
                            active_keys.add(key)
                            self._update_trigger(
                                TriggerSource.EMERGENT, key,
                                f"{trend.metric_name} falling (slope={trend.slope:.4f})"
                            )
            except Exception:
                logger.debug("EmergentDetector check skipped", exc_info=True)

        # 3. Counselor red/yellow alerts
        if self._counselor_fn:
            try:
                assessments = self._counselor_fn()
                for assessment in assessments:
                    if hasattr(assessment, 'alert_level') and assessment.alert_level in ("red", "yellow"):
                        key = f"counselor:{assessment.agent_id}:{assessment.alert_level}"
                        active_keys.add(key)
                        self._update_trigger(
                            TriggerSource.COUNSELOR, key,
                            f"Agent {assessment.agent_id} alert={assessment.alert_level}"
                        )
            except Exception:
                logger.debug("Counselor check skipped", exc_info=True)

        # Clear resolved triggers
        resolved = [k for k in self._triggers if k not in active_keys]
        for k in resolved:
            del self._triggers[k]

        # Generate proposals for persistent triggers
        for key, trigger in self._triggers.items():
            if trigger.consecutive_count == self._persistence_threshold:
                self._generate_proposal(trigger)

    def _update_trigger(self, source: TriggerSource, key: str, detail: str) -> None:
        """Update or create a trigger state."""
        if key in self._triggers:
            self._triggers[key].consecutive_count += 1
            self._triggers[key].last_seen = time.time()
            self._triggers[key].detail = detail
        else:
            self._triggers[key] = TriggerState(source=source, key=key, detail=detail)

    def _generate_proposal(self, trigger: TriggerState) -> None:
        """Generate a RemediationProposal from a persistent trigger."""
        action_type, gate, severity = self._classify_trigger(trigger)
        proposal = RemediationProposal(
            id=str(uuid.uuid4()),
            trigger_source=trigger.source,
            trigger_detail=trigger.detail,
            action_type=action_type,
            action_detail=self._describe_action(trigger, action_type),
            gate=gate,
            severity=severity,
            persistence_count=trigger.consecutive_count,
        )
        self._proposals.append(proposal)
        # Keep only last 50 proposals
        if len(self._proposals) > 50:
            self._proposals = self._proposals[-50:]
        if self._on_proposal:
            self._on_proposal(proposal)
        if self._on_event:
            self._on_event({"type": EventType.INITIATIVE_PROPOSAL.value, "data": proposal.to_dict()})
        logger.info("Initiative proposal: %s → %s (%s)", trigger.key, action_type.value, gate.value)

    def _classify_trigger(self, trigger: TriggerState) -> tuple[ActionType, ActionGate, str]:
        """Classify trigger into action type, gate level, and severity.

        Rules:
        - SIF violations → DIAGNOSE (AUTO), severity=high
        - Emergent falling trends → ALERT_CAPTAIN (AUTO), severity=medium
        - Counselor red alert → RECYCLE (COMMANDER), severity=high
        - Counselor yellow alert → DIAGNOSE (AUTO), severity=medium
        """
        if trigger.source == TriggerSource.SIF:
            return ActionType.DIAGNOSE, ActionGate.AUTO, "high"
        elif trigger.source == TriggerSource.EMERGENT:
            return ActionType.ALERT_CAPTAIN, ActionGate.AUTO, "medium"
        elif trigger.source == TriggerSource.COUNSELOR:
            if "red" in trigger.key:
                return ActionType.RECYCLE, ActionGate.COMMANDER, "high"
            else:
                return ActionType.DIAGNOSE, ActionGate.AUTO, "medium"
        return ActionType.ALERT_CAPTAIN, ActionGate.AUTO, "low"

    def _describe_action(self, trigger: TriggerState, action_type: ActionType) -> str:
        """Generate human-readable action description."""
        descriptions = {
            ActionType.DIAGNOSE: f"Run diagnostics on: {trigger.detail}",
            ActionType.SCALE: f"Adjust pool sizing for: {trigger.detail}",
            ActionType.RECYCLE: f"Recycle degraded agent: {trigger.detail}",
            ActionType.PATCH: f"Trigger self-mod pipeline for: {trigger.detail}",
            ActionType.ALERT_CAPTAIN: f"Captain attention needed: {trigger.detail}",
        }
        return descriptions.get(action_type, trigger.detail)

    @property
    def active_triggers(self) -> list[TriggerState]:
        """Return currently active triggers."""
        return list(self._triggers.values())

    @property
    def recent_proposals(self) -> list[RemediationProposal]:
        """Return recent proposals."""
        return list(self._proposals)

    def approve_proposal(self, proposal_id: str) -> bool:
        """Approve a proposal (Captain action). Returns True if found."""
        for p in self._proposals:
            if p.id == proposal_id:
                p.status = "approved"
                return True
        return False

    def reject_proposal(self, proposal_id: str) -> bool:
        """Reject a proposal (Captain action). Returns True if found."""
        for p in self._proposals:
            if p.id == proposal_id:
                p.status = "rejected"
                return True
        return False
