"""Container monitoring and health check functionality."""

from __future__ import annotations

import threading
import time
from collections.abc import Callable
from datetime import datetime
from typing import TYPE_CHECKING

from gaol.models.container import (
    ContainerStats,
    HealthCheck,
    HealthCheckResult,
    HealthStatus,
)

if TYPE_CHECKING:
    from gaol.runtimes.base import ContainerRuntime


class HealthChecker:
    """Runs health checks against a container."""

    def __init__(
        self,
        runtime: ContainerRuntime,
        container_id: str,
        health_check: HealthCheck,
    ) -> None:
        """Initialize health checker.

        Args:
            runtime: The container runtime to use
            container_id: Container to check
            health_check: Health check configuration
        """
        self.runtime = runtime
        self.container_id = container_id
        self.health_check = health_check
        self._consecutive_failures = 0
        self._last_result: HealthCheckResult | None = None
        self._started_at: datetime | None = None

    @property
    def last_result(self) -> HealthCheckResult | None:
        """Get the last health check result."""
        return self._last_result

    def check(self) -> HealthCheckResult:
        """Run a single health check.

        Returns:
            HealthCheckResult with the check status
        """
        if self._started_at is None:
            self._started_at = datetime.now()

        # Check if we're still in the start period
        elapsed = (datetime.now() - self._started_at).total_seconds()
        if elapsed < self.health_check.start_period_seconds:
            self._last_result = HealthCheckResult(
                status=HealthStatus.STARTING,
                consecutive_failures=self._consecutive_failures,
            )
            return self._last_result

        try:
            # Run the health check command with timeout
            exit_code, stdout, stderr = self.runtime.exec(
                self.container_id,
                self.health_check.command,
            )

            output = stdout + stderr if stderr else stdout

            if exit_code == 0:
                self._consecutive_failures = 0
                self._last_result = HealthCheckResult(
                    status=HealthStatus.HEALTHY,
                    exit_code=exit_code,
                    output=output[:500] if output else None,  # Truncate long output
                    consecutive_failures=0,
                )
            else:
                self._consecutive_failures += 1
                status = (
                    HealthStatus.UNHEALTHY
                    if self._consecutive_failures >= self.health_check.retries
                    else HealthStatus.STARTING
                )
                self._last_result = HealthCheckResult(
                    status=status,
                    exit_code=exit_code,
                    output=output[:500] if output else None,
                    consecutive_failures=self._consecutive_failures,
                )

        except RuntimeError as e:
            self._consecutive_failures += 1
            status = (
                HealthStatus.UNHEALTHY
                if self._consecutive_failures >= self.health_check.retries
                else HealthStatus.STARTING
            )
            self._last_result = HealthCheckResult(
                status=status,
                output=str(e)[:500],
                consecutive_failures=self._consecutive_failures,
            )

        return self._last_result

    def reset(self) -> None:
        """Reset health check state."""
        self._consecutive_failures = 0
        self._last_result = None
        self._started_at = None


class ContainerMonitor:
    """Monitors container health and resource usage."""

    def __init__(
        self,
        runtime: ContainerRuntime,
        container_id: str,
        health_check: HealthCheck | None = None,
        stats_callback: Callable[[ContainerStats], None] | None = None,
        health_callback: Callable[[HealthCheckResult], None] | None = None,
        stats_interval: float = 5.0,
    ) -> None:
        """Initialize container monitor.

        Args:
            runtime: The container runtime to use
            container_id: Container to monitor
            health_check: Optional health check configuration
            stats_callback: Called with stats updates
            health_callback: Called with health check results
            stats_interval: Interval between stats collection in seconds
        """
        self.runtime = runtime
        self.container_id = container_id
        self.health_check = health_check
        self.stats_callback = stats_callback
        self.health_callback = health_callback
        self.stats_interval = stats_interval

        self._health_checker: HealthChecker | None = None
        if health_check:
            self._health_checker = HealthChecker(runtime, container_id, health_check)

        self._running = False
        self._stats_thread: threading.Thread | None = None
        self._health_thread: threading.Thread | None = None

    @property
    def is_running(self) -> bool:
        """Check if monitor is running."""
        return self._running

    def start(self) -> None:
        """Start monitoring the container."""
        if self._running:
            return

        self._running = True

        # Start stats collection thread
        if self.stats_callback:
            self._stats_thread = threading.Thread(
                target=self._collect_stats_loop,
                daemon=True,
            )
            self._stats_thread.start()

        # Start health check thread
        if self._health_checker and self.health_callback:
            self._health_thread = threading.Thread(
                target=self._health_check_loop,
                daemon=True,
            )
            self._health_thread.start()

    def stop(self) -> None:
        """Stop monitoring the container."""
        self._running = False

        if self._stats_thread:
            self._stats_thread.join(timeout=2.0)
            self._stats_thread = None

        if self._health_thread:
            self._health_thread.join(timeout=2.0)
            self._health_thread = None

    def _collect_stats_loop(self) -> None:
        """Collect stats in a loop."""
        while self._running:
            try:
                stats = self.runtime.stats(self.container_id)
                if self.stats_callback:
                    self.stats_callback(stats)
            except (RuntimeError, NotImplementedError):
                # Container might have stopped
                pass

            time.sleep(self.stats_interval)

    def _health_check_loop(self) -> None:
        """Run health checks in a loop."""
        if not self._health_checker or not self.health_check:
            return

        while self._running:
            result = self._health_checker.check()
            if self.health_callback:
                self.health_callback(result)

            time.sleep(self.health_check.interval_seconds)

    def get_stats(self) -> ContainerStats | None:
        """Get current container stats.

        Returns:
            ContainerStats or None if not available
        """
        try:
            return self.runtime.stats(self.container_id)
        except (RuntimeError, NotImplementedError):
            return None

    def get_health(self) -> HealthCheckResult | None:
        """Get current health status.

        Returns:
            HealthCheckResult or None if no health check configured
        """
        if self._health_checker:
            return self._health_checker.last_result
        return None

    def check_health_now(self) -> HealthCheckResult | None:
        """Run a health check immediately.

        Returns:
            HealthCheckResult or None if no health check configured
        """
        if self._health_checker:
            return self._health_checker.check()
        return None


def create_http_health_check(
    port: int = 80,
    path: str = "/health",
    timeout: int = 10,
    interval: int = 30,
    retries: int = 3,
) -> HealthCheck:
    """Create an HTTP health check configuration.

    Args:
        port: HTTP port to check
        path: HTTP path to request
        timeout: Request timeout in seconds
        interval: Check interval in seconds
        retries: Number of failures before unhealthy

    Returns:
        HealthCheck configuration
    """
    return HealthCheck(
        command=["curl", "-sf", f"http://localhost:{port}{path}"],
        timeout_seconds=timeout,
        interval_seconds=interval,
        retries=retries,
    )


def create_tcp_health_check(
    port: int,
    host: str = "localhost",
    timeout: int = 10,
    interval: int = 30,
    retries: int = 3,
) -> HealthCheck:
    """Create a TCP health check configuration.

    Args:
        port: TCP port to check
        host: Host to connect to
        timeout: Connection timeout in seconds
        interval: Check interval in seconds
        retries: Number of failures before unhealthy

    Returns:
        HealthCheck configuration
    """
    # Use bash to check if port is open
    return HealthCheck(
        command=[
            "bash", "-c",
            f"timeout {timeout} bash -c 'cat < /dev/null > /dev/tcp/{host}/{port}'"
        ],
        timeout_seconds=timeout,
        interval_seconds=interval,
        retries=retries,
    )


def create_command_health_check(
    command: list[str],
    timeout: int = 10,
    interval: int = 30,
    retries: int = 3,
    start_period: int = 0,
) -> HealthCheck:
    """Create a custom command health check configuration.

    Args:
        command: Command to run for health check
        timeout: Command timeout in seconds
        interval: Check interval in seconds
        retries: Number of failures before unhealthy
        start_period: Grace period after start in seconds

    Returns:
        HealthCheck configuration
    """
    return HealthCheck(
        command=command,
        timeout_seconds=timeout,
        interval_seconds=interval,
        retries=retries,
        start_period_seconds=start_period,
    )
