"""
InsAIts Anchor Trigger Engine
==============================
Determines WHEN to inject anchor messages based on session state,
tool call patterns, anomaly thresholds, and randomized cadences.

Three layers:
  Layer 1 (Opus/orchestrator) — periodic + anomaly + session start + critical
  Layer 2 (Agents)            — agent spawn + anomaly threshold
  Layer 3 (Subagents)         — subagent spawn only

Premium implementation — proprietary, not published to GitHub.
Author: Cristi Bogdan -- Steddy Nova SRL / YuyAI
"""

from __future__ import annotations

import logging
import random
import time
from typing import Any, Dict, List, Optional, Set

# Import TriggerType from the shared _types module to avoid circular imports.
# Both facade and premium import from the same source → enum identity guaranteed.
from insa_its.anchor._types import TriggerType

logger = logging.getLogger("insaits.anchor.trigger")


class AnchorTriggerEngine:
    """Evaluates tool call context and returns which layers to inject.

    Stateful: tracks tool call count, anomaly deltas, pending delayed
    injections, and injection history for dedup.
    """

    def __init__(
        self,
        opus_refresh_min: int = 40,
        opus_refresh_max: int = 80,
        anomaly_delta_threshold: int = 25,
        anomaly_delay_max_seconds: int = 45,
        stealth_disables_injection: bool = True,
        layer1_enabled: bool = True,
        layer2_enabled: bool = True,
        layer3_enabled: bool = True,
        subagent_refresh_min: int = 8,
        subagent_refresh_max: int = 15,
    ) -> None:
        self._opus_min = opus_refresh_min
        self._opus_max = opus_refresh_max
        self._subagent_min = subagent_refresh_min
        self._subagent_max = subagent_refresh_max
        self._anomaly_delta = anomaly_delta_threshold
        self._anomaly_delay_max = anomaly_delay_max_seconds
        self._stealth_disables = stealth_disables_injection
        self._layer1_enabled = layer1_enabled
        self._layer2_enabled = layer2_enabled
        self._layer3_enabled = layer3_enabled

        # Session state
        self._tool_call_count: int = 0
        self._subagent_call_count: int = 0
        self._next_opus_refresh_at: int = self._random_opus_interval()
        self._next_subagent_refresh_at: int = self._random_subagent_interval()
        self._last_anomaly_count: int = 0
        self._pending_injections: List[Dict[str, Any]] = []
        self._injection_history: List[Dict[str, Any]] = []
        self._is_first_call: bool = True
        self._seen_agents: Set[str] = set()

    def _random_opus_interval(self) -> int:
        """Generate a random interval for next Opus refresh.

        Never reuses the same interval twice in a row.
        """
        interval = random.randint(self._opus_min, self._opus_max)
        return self._tool_call_count + interval

    def _random_subagent_interval(self) -> int:
        """Generate a random interval for next subagent (L3) refresh.

        Subagents get more frequent anchoring (8-15 calls) since they
        operate with less context and are more prone to drift.
        """
        interval = random.randint(self._subagent_min, self._subagent_max)
        return self._subagent_call_count + interval

    def evaluate(
        self,
        tool_name: str,
        tool_input: Dict[str, Any],
        anomaly_count: int = 0,
        is_subagent: bool = False,
        agent_id: str = "",
        stealth: bool = False,
        has_critical: bool = False,
    ) -> List[Dict[str, Any]]:
        """Evaluate current context and return list of triggered injections.

        Returns a list of dicts:
            [{"layers": [1,2,3], "trigger": TriggerType, "delay": float}]

        Empty list = no injection needed.
        """
        self._tool_call_count += 1

        if stealth and self._stealth_disables:
            self._is_first_call = False
            return []

        triggers: List[Dict[str, Any]] = []

        # 1. Session start: all 3 layers
        if self._is_first_call:
            self._is_first_call = False
            layers = self._enabled_layers([1, 2, 3])
            if layers:
                triggers.append({
                    "layers": layers,
                    "trigger": TriggerType.SESSION_START,
                    "delay": 0.0,
                })
            return triggers

        # 2. Agent spawn: Layer 2
        if tool_name == "Agent" and not is_subagent:
            subagent_type = tool_input.get("subagent_type", "unknown")
            agent_key = f"{subagent_type}:{tool_input.get('description', '')[:50]}"
            if agent_key not in self._seen_agents:
                self._seen_agents.add(agent_key)
                layers = self._enabled_layers([2])
                if layers:
                    triggers.append({
                        "layers": layers,
                        "trigger": TriggerType.AGENT_SPAWN,
                        "delay": 0.0,
                    })

        # 3. Subagent context: Layer 3
        if is_subagent and agent_id and agent_id not in self._seen_agents:
            self._seen_agents.add(agent_id)
            layers = self._enabled_layers([3])
            if layers:
                triggers.append({
                    "layers": layers,
                    "trigger": TriggerType.SUBAGENT_SPAWN,
                    "delay": 0.0,
                })

        # 4. Critical event: Layer 1 + human pause
        if has_critical:
            layers = self._enabled_layers([1])
            if layers:
                triggers.append({
                    "layers": layers,
                    "trigger": TriggerType.CRITICAL_EVENT,
                    "delay": 0.0,
                })

        # 5. Anomaly threshold crossed: Layer 1+2 with random delay
        if anomaly_count > 0:
            delta = anomaly_count - self._last_anomaly_count
            if delta >= self._anomaly_delta:
                self._last_anomaly_count = anomaly_count
                delay = random.uniform(0, self._anomaly_delay_max)
                layers = self._enabled_layers([1, 2])
                if layers:
                    triggers.append({
                        "layers": layers,
                        "trigger": TriggerType.ANOMALY_THRESHOLD,
                        "delay": delay,
                    })

        # 6. Periodic Opus refresh: Layer 1 only
        if self._tool_call_count >= self._next_opus_refresh_at:
            self._next_opus_refresh_at = self._random_opus_interval()
            layers = self._enabled_layers([1])
            if layers:
                triggers.append({
                    "layers": layers,
                    "trigger": TriggerType.OPUS_PERIODIC,
                    "delay": 0.0,
                })

        # 7. Periodic subagent refresh: Layer 3 only
        #    Subagents get more frequent anchoring since they have less
        #    context and are more prone to drift/hallucination.
        if is_subagent:
            self._subagent_call_count += 1
            if self._subagent_call_count >= self._next_subagent_refresh_at:
                self._next_subagent_refresh_at = self._random_subagent_interval()
                layers = self._enabled_layers([3])
                if layers:
                    triggers.append({
                        "layers": layers,
                        "trigger": TriggerType.SUBAGENT_PERIODIC,
                        "delay": 0.0,
                    })

        # Process pending delayed injections
        self._check_pending(triggers)

        # Record to history (dedup)
        for t in triggers:
            self._injection_history.append({
                "ts": time.time(),
                "layers": t["layers"],
                "trigger": t["trigger"].value,
                "tool_call_count": self._tool_call_count,
            })
        # Keep history bounded
        self._injection_history = self._injection_history[-10:]

        return triggers

    def schedule_delayed(
        self, layers: List[int], trigger: TriggerType, delay: float
    ) -> None:
        """Queue a delayed injection for later firing."""
        fire_at = time.time() + delay
        self._pending_injections.append({
            "layers": layers,
            "trigger": trigger,
            "fire_at": fire_at,
        })

    def _check_pending(self, triggers: List[Dict[str, Any]]) -> None:
        """Fire any pending delayed injections whose time has come."""
        now = time.time()
        still_pending = []
        for p in self._pending_injections:
            if now >= p["fire_at"]:
                triggers.append({
                    "layers": p["layers"],
                    "trigger": p["trigger"],
                    "delay": 0.0,
                })
            else:
                still_pending.append(p)
        self._pending_injections = still_pending

    def _enabled_layers(self, requested: List[int]) -> List[int]:
        """Filter requested layers to only those enabled in config."""
        enabled_map = {
            1: self._layer1_enabled,
            2: self._layer2_enabled,
            3: self._layer3_enabled,
        }
        return [l for l in requested if enabled_map.get(l, False)]

    @property
    def tool_call_count(self) -> int:
        return self._tool_call_count

    @property
    def next_opus_refresh_at(self) -> int:
        return self._next_opus_refresh_at

    @property
    def injection_history(self) -> List[Dict[str, Any]]:
        return list(self._injection_history)

    def get_state(self) -> Dict[str, Any]:
        """Serialize current state for dashboard/debugging."""
        return {
            "tool_call_count": self._tool_call_count,
            "subagent_call_count": self._subagent_call_count,
            "next_opus_refresh_at": self._next_opus_refresh_at,
            "next_subagent_refresh_at": self._next_subagent_refresh_at,
            "last_anomaly_count": self._last_anomaly_count,
            "pending_injections": len(self._pending_injections),
            "injection_count": len(self._injection_history),
            "seen_agents": len(self._seen_agents),
        }
