import asyncio
import logging
import threading
import time
import os
import subprocess
import sys
from pathlib import Path
from typing import Dict, Any, Optional

from brynq.agent_loop import BrynqAgent

log = logging.getLogger(__name__)

class AgentSupervisor:
    """
    Manages background agent processes via CLI for the Integrated Runtime.
    Provides auto-restart (Defibrillator) and silent execution.
    """
    def __init__(self):
        self._threads: Dict[str, threading.Thread] = {}
        self._processes: Dict[str, Any] = {}
        self._configs: Dict[str, dict] = {}
        self._stop_events: Dict[str, threading.Event] = {}
        self._watchdog_thread: Optional[threading.Thread] = None
        self._watchdog_stop = threading.Event()

    def start_agent(self, name: str, backend: str, model: str, channel: str, system_prompt: str = "") -> bool:
        if name in self._threads and self._threads[name].is_alive():
            log.warning(f"Agent {name} is already running.")
            return False

        log.info(f"Supervisor starting agent: {name} on #{channel}")
        
        stop_event = threading.Event()
        self._stop_events[name] = stop_event
        
        # Deduce platform from backend
        platform = "api"
        if backend in ("claude", "gemini"):
            platform = backend
        elif backend == "ollama":
            platform = "local"
            
        self._configs[name] = {
            "backend": backend,
            "model": model,
            "channel": channel,
            "system_prompt": system_prompt,
            "platform": platform
        }

        def run_loop():
            while not stop_event.is_set():
                try:
                    log.info(f"Supervisor launching agent loop for {name}")
                    
                    # We always use the native Brynq Python loop now that API keys are set
                    cmd = [
                        sys.executable, "-u", "-m", "brynq.agent_loop", 
                        "--name", name, 
                        "--backend", backend, 
                        "--model", model, 
                        "--channel", channel
                    ]
                    if system_prompt:
                        cmd.extend(["--system-prompt", system_prompt])
                        
                    env = os.environ.copy()
                    env["BROKER_SESSION_NAME"] = name
                    env["BROKER_PLATFORM"] = platform
                    if channel:
                        env["BRYNQ_SWARM"] = channel # Using channel as swarm name for routing
                        
                    log_dir = Path("state/broker/logs")
                    log_dir.mkdir(parents=True, exist_ok=True)
                    log_file = log_dir / f"{name}.log"
                    
                    f = open(log_file, "a")
                    try:
                        process = subprocess.Popen(
                            cmd,
                            env=env,
                            stdout=f,
                            stderr=subprocess.STDOUT,
                            creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
                        )
                    except Exception:
                        f.close()
                        raise

                    # Parent doesn't need the FD — child inherited it
                    f.close()

                    self._processes[name] = process

                    # Wait for process to finish or be stopped
                    while process.poll() is None and not stop_event.is_set():
                        time.sleep(1)

                    if stop_event.is_set() and process.poll() is None:
                        process.terminate()
                        try:
                            process.wait(timeout=3)
                        except subprocess.TimeoutExpired:
                            process.kill()
                            
                except Exception as e:
                    log.error(f"Agent {name} crashed: {e}. Supervisor will restart it.")
                    time.sleep(5)

        thread = threading.Thread(target=run_loop, name=f"AgentThread-{name}", daemon=True)
        thread.start()
        self._threads[name] = thread
        
        return True

    def stop_agent(self, name: str) -> bool:
        if name in self._stop_events:
            log.info(f"Supervisor stopping agent: {name}")
            self._stop_events[name].set()
            
            # Kill subprocess if exists
            process = self._processes.get(name)
            if process and process.poll() is None:
                try:
                    process.terminate()
                except Exception:
                    pass
                    
            if name in self._threads:
                self._threads[name].join(timeout=2.0)
                del self._threads[name]
            del self._stop_events[name]
            if name in self._configs:
                del self._configs[name]
            return True
        return False

    def list_agents(self) -> list[dict]:
        return [
            {
                "name": name,
                "alive": thread.is_alive(),
                "session_id": f"supervisor-{name}"
            }
            for name, thread in self._threads.items()
        ]

    def _watchdog_loop(self):
        """Monitors threads and restarts them if they die unexpectedly. Also runs Oroboros at 3 AM."""
        import datetime
        last_oroboros_date = None

        while not self._watchdog_stop.is_set():
            # 1. Defibrillator: Restart dead agents
            for name, thread in list(self._threads.items()):
                if not thread.is_alive() and not self._stop_events[name].is_set():
                    log.warning(f"Watchdog detected dead agent: {name}. Restarting.")
                    # Grab config before deleting
                    config = self._configs.get(name)
                    if config:
                        backend = config["backend"]
                        model = config["model"]
                        channel = config["channel"]
                        system_prompt = config["system_prompt"]
                        
                        # Cleanup old state
                        self.stop_agent(name)
                        
                        # Respawn
                        self.start_agent(name, backend, model, channel, system_prompt)
            
            # 2. Oroboros Self-Improvement: Run once a day at 3 AM local time
            now = datetime.datetime.now()
            if now.hour == 3 and last_oroboros_date != now.date():
                log.info("Watchdog: Idle time detected (3 AM). Triggering Oroboros self-improvement cycle.")
                try:
                    try:
                        from brynq.oroboros import run_oroboros_cycle
                    except ImportError:
                        from brynq._future.oroboros import run_oroboros_cycle
                    
                    def _run_oro():
                        try:
                            result = run_oroboros_cycle()
                            log.info(f"Oroboros cycle completed: {result}")
                        except Exception as e:
                            log.error(f"Oroboros cycle failed: {e}")

                    threading.Thread(target=_run_oro, daemon=True, name="OroborosCycle").start()
                    last_oroboros_date = now.date()
                except ImportError:
                    log.warning("Oroboros module not found, skipping self-improvement cycle.")

            time.sleep(10)

    def start_watchdog(self):
        if self._watchdog_thread is None or not self._watchdog_thread.is_alive():
            self._watchdog_stop.clear()
            self._watchdog_thread = threading.Thread(target=self._watchdog_loop, name="SupervisorWatchdog", daemon=True)
            self._watchdog_thread.start()
            log.info("Supervisor Watchdog started.")

    def stop_all(self):
        self._watchdog_stop.set()
        for name in list(self._stop_events.keys()):
            self.stop_agent(name)

# Global singleton instance for the API server to use
supervisor = AgentSupervisor()