"""
Doit Agent - Supervisor Module
Manages process lifecycle, crash recovery, graceful shutdown, and health checks.
"""
from __future__ import annotations

import asyncio
import logging
import signal
import sys
import time
from typing import Optional

logger = logging.getLogger("doit.supervisor")


class Supervisor:
    """
    Coordinates startup, shutdown, and crash recovery for all Doit components.
    Handles SIGTERM/SIGINT for graceful shutdown.
    Persists state before exit.
    """

    def __init__(self):
        self._components = []
        self._shutdown_event = asyncio.Event()
        self._startup_time = time.time()
        self._restart_count = 0

    def add_component(self, name: str, coro_factory):
        """Register an async component to supervise."""
        self._components.append((name, coro_factory))

    async def run(self) -> None:
        """Start all components and supervise them."""
        loop = asyncio.get_running_loop()

        # Register signal handlers
        if sys.platform != "win32":
            loop.add_signal_handler(signal.SIGTERM, self._handle_signal)
            loop.add_signal_handler(signal.SIGINT, self._handle_signal)
        else:
            # Windows: use KeyboardInterrupt
            signal.signal(signal.SIGTERM, lambda *_: self._handle_signal())

        logger.info("Supervisor starting %d components", len(self._components))

        tasks = []
        for name, factory in self._components:
            task = asyncio.create_task(
                self._supervised(name, factory),
                name=f"doit.{name}"
            )
            tasks.append(task)

        # Wait for shutdown signal
        await self._shutdown_event.wait()

        logger.info("Shutdown signal received. Stopping components...")
        for task in tasks:
            task.cancel()

        await asyncio.gather(*tasks, return_exceptions=True)
        logger.info("All components stopped. Uptime: %.1fs", time.time() - self._startup_time)

    async def _supervised(self, name: str, factory) -> None:
        """Run a component, restarting on crash (unless shutdown requested)."""
        restart_delay = 2
        while not self._shutdown_event.is_set():
            try:
                logger.info("Starting component: %s", name)
                await factory()
            except asyncio.CancelledError:
                logger.info("Component %s cancelled", name)
                return
            except Exception as e:
                if self._shutdown_event.is_set():
                    return
                self._restart_count += 1
                logger.error(
                    "Component %s crashed: %s — restarting in %ds",
                    name, e, restart_delay
                )
                await asyncio.sleep(restart_delay)
                restart_delay = min(restart_delay * 2, 60)

    def _handle_signal(self) -> None:
        logger.info("Signal received — initiating graceful shutdown")
        self._shutdown_event.set()

    def trigger_shutdown(self) -> None:
        self._shutdown_event.set()

    async def wait_for_shutdown(self) -> None:
        await self._shutdown_event.wait()


class HealthMonitor:
    """Periodic health check that notifies user if something is wrong."""

    def __init__(self, notify_callback=None):
        self._notify = notify_callback
        self._running = False

    async def run(self) -> None:
        self._running = True
        while self._running:
            try:
                await self._check()
            except Exception as e:
                logger.warning("Health check error: %s", e)
            await asyncio.sleep(300)  # every 5 minutes

    async def _check(self) -> None:
        import psutil
        alerts = []

        cpu = psutil.cpu_percent(interval=1)
        if cpu > 90:
            alerts.append(f"⚠️ CPU at {cpu:.1f}%")

        mem = psutil.virtual_memory()
        if mem.percent > 90:
            alerts.append(f"⚠️ RAM at {mem.percent:.1f}%")

        disk = psutil.disk_usage("/")
        if disk.percent > 90:
            alerts.append(f"⚠️ Disk at {disk.percent:.1f}%")

        if alerts and self._notify:
            msg = "🚨 System Health Alert:\n" + "\n".join(alerts)
            try:
                await self._notify(msg)
            except Exception:
                pass

    def stop(self) -> None:
        self._running = False
