import threading
import time
from typing import List, Dict, Optional
from pathlib import Path

from altrus.core.intent.intent import Intent, IntentState
from altrus.core.intent.policies.default_policy import DefaultRoutingPolicy
from altrus.core.intent.storage import IntentStorage
from altrus.core.observability.metrics import (
    intents_total,
    intents_created_total,
    intent_executing,
)




class IntentEngine:
    """
    Authoritative runtime intent execution engine.
    Manages intent lifecycle transitions.
    """

    def __init__(
        self,
        registry,
        ledger,
        policy: Optional[DefaultRoutingPolicy] = None,
        preemption_config: dict | None = None,
        storage_dir: Optional[Path] = None,
    ):
        self.registry = registry
        self.ledger = ledger
        self.storage_dir = storage_dir

        self.policy = policy or DefaultRoutingPolicy()
        self.preemption_config = preemption_config or {}

        # Persistent storage
        self.storage = IntentStorage(storage_dir=storage_dir)

        # Concurrency safety
        self._lock = threading.Lock()

        # Load persisted intents
        loaded_intents = self.storage.load()
        self.intent_store: dict[str, Intent] = {
            i.intent_id: i for i in loaded_intents
        }

        # Restore currently executing intent (if any)
        self.current_intent: Optional[Intent] = next(
            (i for i in self.intent_store.values()
            if i.state == IntentState.EXECUTING),
            None
        )

        # -------------------------------
        # SLA & Retry Management
        # -------------------------------
        self._running = True
        self._monitor_thread = threading.Thread(target=self._sla_monitor_loop, daemon=True)
        self._monitor_thread.start()

    def _sla_monitor_loop(self):
        """Background loop to check for SLA violations"""
        import time as time_module
        while self._running:
            time_module.sleep(2.0)

            violations = []
            for intent in list(self.intent_store.values()):
                if intent.state in [IntentState.SCHEDULED, IntentState.EXECUTING]:
                    if intent.is_sla_violated():
                        violations.append(intent)

            for intent in violations:
                self._handle_sla_violation(intent)

    def _handle_sla_violation(self, intent: Intent):
        """Handle an intent that exceeded its SLA"""
        self.ledger.log_event(
            event_type="INTENT_SLA_VIOLATION",
            actor_type="INTENT",
            actor_id=intent.intent_id,
            data={
                "name": intent.name,
                "elapsed": time.time() - intent.created_at,
                "sla_timeout": intent.sla_timeout
            }
        )

        # If executing, we might want to preempt or just flag
        if intent.state == IntentState.EXECUTING:
            self.current_intent = None # Clear current

        # Check for retry
        if intent.retry_count < intent.max_retries:
            self._retry_intent(intent, reason="SLA_VIOLATION")
        else:
            intent.state = IntentState.EXPIRED
            self.storage.save(self.intent_store.values())

    def _retry_intent(self, intent: Intent, reason: str):
        """Retry an intent with exponential backoff"""
        intent.retry_count += 1
        wait_time = 2 ** intent.retry_count # Exponential backoff

        self.ledger.log_event(
            event_type="INTENT_RETRY_SCHEDULED",
            actor_type="INTENT",
            actor_id=intent.intent_id,
            data={
                "reason": reason,
                "attempt": intent.retry_count,
                "wait_time": wait_time
            }
        )

        # In this mock system, we just put it back to SCHEDULED
        # In a real system, we would use a delayed queue
        intent.state = IntentState.SCHEDULED
        self.storage.save(self.intent_store.values())

    def shutdown(self):
        self._running = False


    # -------------------------------
    # Public API
    # -------------------------------

    def submit_intent(self, intent: Intent):
        """Submit a new intent to the engine"""
        with self._lock:
            # Check for duplicates or immediate rejection
            if (
                self.current_intent
                and self.current_intent.capability == intent.capability
            ):
                intent.state = IntentState.REJECTED

                self.ledger.log_event(
                    event_type="INTENT_REJECTED",
                    actor_type="INTENT",
                    actor_id=intent.intent_id,
                    data={"reason": "DUPLICATE_CAPABILITY"},
                )

                self.intent_store[intent.intent_id] = intent
                self.storage.save(list(self.intent_store.values()))
                return

            # Otherwise, accept the intent
            intent.state = IntentState.SCHEDULED
            self.intent_store[intent.intent_id] = intent

            self.ledger.log_event(
                event_type="INTENT_SUBMITTED",
                actor_type="INTENT",
                actor_id=intent.intent_id,
                data={
                    "name": intent.name,
                    "capability": intent.capability,
                    "priority": intent.priority.name,
                    "criticality": intent.criticality.value
                }
            )

            self.storage.save(list(self.intent_store.values()))

            # ✅ METRICS
            intents_created_total.inc()
            intents_total.set(len(self.intent_store))


        self.ledger.log_event(
            event_type="INTENT_SUBMITTED",
            actor_type="INTENT",
            actor_id=intent.intent_id,
            data={
                "name": intent.name,
                "capability": intent.capability,
                "priority": intent.priority.name,
                "criticality": intent.criticality.value,
                "state": intent.state.value,
            },
        )

    def list_intents(self) -> List[Intent]:
        return list(self.intent_store.values())

    # -------------------------------
    # Fault Interaction
    # -------------------------------

    def on_module_failed(self, module_id: str):
        if not self.current_intent:
            return

        self.current_intent.mark_preempted()

        self.ledger.log_event(
            event_type="INTENT_PREEMPTED",
            actor_type="INTENT",
            actor_id=self.current_intent.intent_id,
            data={
                "reason": "MODULE_FAILURE",
                "module_id": module_id,
            },
        )

        self.storage.save(self.intent_store.values())
        self.current_intent = None

    def mark_intent_completed(self, intent_id: str):
        """Mark an intent as successfully completed"""
        with self._lock:
            if intent_id not in self.intent_store:
                return

            intent = self.intent_store[intent_id]
            intent.state = IntentState.COMPLETED

            self.ledger.log_event(
                event_type="INTENT_COMPLETED",
                actor_type="INTENT",
                actor_id=intent_id,
                data={
                    "name": intent.name,
                    "capability": intent.capability
                }
            )

            if self.current_intent and self.current_intent.intent_id == intent_id:
                self.current_intent = None

            self.storage.save(self.intent_store.values())

    def mark_intent_failed(self, intent_id: str, reason: str = "Unknown error"):
        """Mark an intent as failed"""
        with self._lock:
            if intent_id not in self.intent_store:
                return

            intent = self.intent_store[intent_id]
            intent.state = IntentState.FAILED

            self.ledger.log_event(
                event_type="INTENT_FAILED",
                actor_type="INTENT",
                actor_id=intent_id,
                data={
                    "name": intent.name,
                    "capability": intent.capability,
                    "reason": reason
                }
            )

            if self.current_intent and self.current_intent.intent_id == intent_id:
                self.current_intent = None

            self.storage.save(self.intent_store.values())

    # -------------------------------
    # Execution Logic
    # -------------------------------

    def route_intent(self, intent: Intent):
        candidates = self.registry.find_by_capability(intent.capability)
        module = self.policy.select_module(intent, candidates)

        if not module:
            return None

        self.ledger.log_event(
            event_type="INTENT_ROUTED",
            actor_type="INTENT",
            actor_id=intent.intent_id,
            data={
                "capability": intent.capability,
                "selected_module": module.module_id,
                "policy": self.policy.__class__.__name__,
            },
        )

        return module

    def should_preempt(self, new_intent: Intent) -> bool:
        from altrus.core.intent.intent import IntentPriority, IntentCriticality
        if not self.preemption_config.get("enabled", True):
            return False

        if not self.current_intent:
            return True

        # Rule: Emergency intents preempt non-critical operations
        if (new_intent.priority == IntentPriority.EMERGENCY
            and self.current_intent.criticality == IntentCriticality.NON_CRITICAL):
            return True

        # Deterministic rules: higher priority prevails; for equal priority, critical intents dominate
        if new_intent.priority > self.current_intent.priority:
            return True
        elif new_intent.priority == self.current_intent.priority:
            return (new_intent.criticality == IntentCriticality.CRITICAL
                    and self.current_intent.criticality == IntentCriticality.NON_CRITICAL)

        return False

    def execute_pending_intents(self):
        """
        Promote highest-priority VALIDATED intent to EXECUTING
        if no intent is currently executing and a compatible module exists.
        """

        # ❗ Prevent multiple EXECUTING intents (CLI restarts safe)
        for i in self.intent_store.values():
            if i.state == IntentState.EXECUTING:
                self.current_intent = i
                return

        # ❗ HARD GUARD (redundant but safe)
        if self.current_intent:
            return

        pending = [
            i for i in self.intent_store.values()
            if i.state == IntentState.SCHEDULED
        ]

        if not pending:
            return

        pending.sort(key=lambda i: (
            -i.priority.value,
            0 if i.criticality.value == "CRITICAL" else 1,
            i.created_at
        ))
        intent = pending[0]

        candidates = self.registry.find_by_capability(intent.capability)
        if not candidates:
            return

        module = candidates[0]

        intent.mark_executing()
        self.current_intent = intent

        self.ledger.log_event(
            event_type="INTENT_EXECUTING",
            actor_type="INTENT",
            actor_id=intent.intent_id,
            data={
                "name": intent.name,
                "capability": intent.capability,
                "priority": intent.priority.name,
                "criticality": intent.criticality.value,
                "module": module.module_id,
            },
        )

        self.storage.save(self.intent_store.values())

        intent_executing.set(1 if self.current_intent else 0)



    def handle_intent(self, intent: Intent):
            """
            Authoritative intent handling pipeline.
            """

            # 1️⃣ Validate
            if not intent.validate():
                intent.state = IntentState.REJECTED
                self.intent_store[intent.intent_id] = intent
                self.storage.save(self.intent_store.values())
                return

            # 2️⃣ Preemption decision
            if self.current_intent and not self.should_preempt(intent):
                intent.state = IntentState.REJECTED
                self.intent_store[intent.intent_id] = intent

                self.ledger.log_event(
                    event_type="INTENT_REJECTED",
                    actor_type="INTENT",
                    actor_id=intent.intent_id,
                    data={
                        "reason": "LOWER_PRIORITY_THAN_CURRENT",
                        "current_intent": self.current_intent.intent_id,
                    },
                )

                # Persist rejection
                self.storage.save(self.intent_store.values())
                return

            # 3️⃣ Preempt current intent if exists
            if self.current_intent:
                self.current_intent.mark_preempted()

                self.ledger.log_event(
                    event_type="INTENT_PREEMPTED",
                    actor_type="INTENT",
                    actor_id=self.current_intent.intent_id,
                    data={
                        "preempted_by": intent.intent_id,
                    },
                )

            # 4️⃣ Route intent
            module = self.route_intent(intent)
            if not module:
                intent.state = IntentState.REJECTED
                self.intent_store[intent.intent_id] = intent
                self.storage.save(self.intent_store.values())
                return

            # 5️⃣ Activate intent
            intent.mark_executing()
            self.current_intent = intent
            self.intent_store[intent.intent_id] = intent

            self.ledger.log_event(
                event_type="INTENT_EXECUTING",
                actor_type="INTENT",
                actor_id=intent.intent_id,
                data={
                    "name": intent.name,
                    "capability": intent.capability,
                    "priority": intent.priority.name,
                    "criticality": intent.criticality.value,
                    "module": module.module_id,
                },
            )

            # Persist execution state
            self.storage.save(self.intent_store.values())
