from __future__ import annotations

"""
Copyright (C) 2025 Luciano Guerche

This file is part of rabbitmq-mcp-server.

rabbitmq-mcp-server is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

rabbitmq-mcp-server is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with rabbitmq-mcp-server. If not, see <https://www.gnu.org/licenses/>.
"""

"""Componente de verificação de saúde da conexão RabbitMQ."""

import asyncio
import time
from collections.abc import Awaitable, Callable
from datetime import UTC, datetime
from typing import TYPE_CHECKING

from rabbitmq_mcp_connection.connection.manager import ConnectionManager
from rabbitmq_mcp_connection.logging.config import get_logger
from rabbitmq_mcp_connection.schemas.connection import ConnectionState, HealthStatus

if TYPE_CHECKING:
    from rabbitmq_mcp_connection.http_client import HTTPClientManager
    from rabbitmq_mcp_connection.reconnect import ReconnectionManager

LOGGER = get_logger(__name__)


class HealthChecker:
    """Executa health checks rápidos sobre uma ``ConnectionManager``.

    Supports both AMQP connection health checks and HTTP Management API health checks.
    Can run as a background task with configurable interval for periodic monitoring.
    Triggers reconnection manager on 3+ consecutive failures.

    Args:
        manager: AMQP ConnectionManager instance
        timeout: Health check timeout in seconds (default: 1.0)
        broker_probe: Optional custom broker probe callable
        http_client: Optional HTTPClientManager for HTTP health checks (AC #2)
        check_interval: Interval between health checks in seconds (default: 30)
        reconnection_manager: Optional ReconnectionManager to trigger on failures
    """

    def __init__(
        self,
        manager: ConnectionManager,
        *,
        timeout: float = 1.0,
        broker_probe: Callable[[], Awaitable[bool]] | None = None,
        http_client: HTTPClientManager | None = None,
        check_interval: int = 30,
        reconnection_manager: ReconnectionManager | None = None,
    ) -> None:
        self._manager = manager
        self._timeout = timeout
        self._broker_probe = broker_probe
        self._http_client = http_client
        self._check_interval = check_interval
        self._reconnection_manager = reconnection_manager
        self._background_task: asyncio.Task[None] | None = None
        self._running = False
        self._latest_status: HealthStatus | None = None
        self._consecutive_failures = 0
        self._last_warning_time = 0.0
        self._successful_checks = 0
        self._failed_checks = 0
        self._total_check_duration = 0.0

    async def check_health(self) -> HealthStatus:
        """Execute single health check for AMQP and HTTP connections.

        Returns:
            HealthStatus with amqp_connected, http_connected, and timing info
        """
        start = time.perf_counter()
        is_connected = self._manager.state == ConnectionState.CONNECTED
        broker_available = False
        http_connected = False
        error_message: str | None = None

        # Check AMQP connection (AC #1)
        try:
            broker_available = await asyncio.wait_for(
                self._probe_broker(), timeout=self._timeout
            )
        except TimeoutError:
            error_message = "Broker health check timed out"
        except Exception as exc:  # pragma: no cover - falhas externas reais
            error_message = str(exc)
            LOGGER.warning("health.check_failed", exception=str(exc))

        # Check HTTP API health (AC #2) - only if http_client provided
        if self._http_client is not None:
            try:
                response = await asyncio.wait_for(
                    self._http_client.request(
                        method="GET", path="/api/healthchecks/node", timeout=1
                    ),
                    timeout=self._timeout,
                )
                http_connected = response.status_code == 200
            except TimeoutError:
                error_message = (
                    f"{error_message}; HTTP health check timed out"
                    if error_message
                    else "HTTP health check timed out"
                )
            except Exception as exc:
                error_message = (
                    f"{error_message}; HTTP: {exc}"
                    if error_message
                    else f"HTTP health check failed: {exc}"
                )
                LOGGER.debug("health.http_check_failed", exception=str(exc))
        else:
            # If no HTTP client configured, mark as True (not checked = N/A)
            http_connected = True

        end = time.perf_counter()

        return HealthStatus(
            is_connected=is_connected,
            broker_available=broker_available,
            http_connected=http_connected,
            amqp_connected=is_connected and broker_available,
            latency_ms=(end - start) * 1000,
            last_check=datetime.now(UTC),
            error_message=error_message,
            consecutive_failures=self._consecutive_failures,
        )

    async def _probe_broker(self) -> bool:
        if self._broker_probe is not None:
            return await self._broker_probe()

        connection = self._manager.connection
        channel = self._manager.channel
        if connection is None or getattr(connection, "is_closed", False):
            return False
        if channel is None or getattr(channel, "is_closed", False):
            return False

        # Se ambos permanecem abertos consideramos broker disponível.
        return True

    async def start(self) -> None:
        """Start background health check task (AC #4).

        Starts periodic health checks running every check_interval seconds.
        """
        if self._running:
            LOGGER.warning("health.already_running", msg="Health check already running")
            return

        self._running = True
        self._background_task = asyncio.create_task(self._health_check_loop())
        LOGGER.info(
            "health.service_started",
            interval=self._check_interval,
            msg=f"Health check service started (interval: {self._check_interval}s)",
        )

    async def stop(self) -> None:
        """Stop background health check task gracefully.

        Cancels the background task and waits for it to complete.
        """
        if not self._running:
            return

        self._running = False
        if self._background_task is not None:
            self._background_task.cancel()
            try:
                await self._background_task
            except asyncio.CancelledError:
                pass
            self._background_task = None
        LOGGER.info("health.service_stopped", msg="Health check service stopped")

    def get_status(self) -> HealthStatus | None:
        """Get latest health status synchronously (AC #7).

        Returns:
            Latest HealthStatus from background checks, or None if not started
        """
        return self._latest_status

    def is_healthy(self) -> bool:
        """Check if both AMQP and HTTP are connected (AC #7).

        Returns:
            True if both connections are healthy, False otherwise
        """
        if self._latest_status is None:
            return False
        return bool(self._latest_status.is_healthy)

    async def _health_check_loop(self) -> None:
        """Background loop executing periodic health checks (AC #4).

        Runs continuously until stopped, performing checks every check_interval seconds.
        Tracks consecutive failures and triggers reconnection logic at threshold.
        """
        while self._running:
            try:
                # Execute health check
                status = await self.check_health()
                self._latest_status = status

                # Track metrics
                check_duration = status.latency_ms
                self._total_check_duration += check_duration

                # Determine if check passed
                check_passed = bool(status.is_healthy)

                if check_passed:
                    # Success - reset failure counter (AC #6)
                    if self._consecutive_failures > 0:
                        LOGGER.info(
                            "health.recovered",
                            consecutive_failures=self._consecutive_failures,
                            msg="Connection health recovered",
                        )
                    self._consecutive_failures = 0
                    self._successful_checks += 1
                    LOGGER.debug(
                        "health.check_passed",
                        duration_ms=check_duration,
                        amqp_connected=status.amqp_connected,
                        http_connected=status.http_connected,
                    )
                else:
                    # Failure - increment counter and log (AC #5)
                    self._consecutive_failures += 1
                    self._failed_checks += 1

                    # Rate-limited warning (max 1 per minute)
                    current_time = time.time()
                    if current_time - self._last_warning_time >= 60:
                        LOGGER.warning(
                            "health.check_failed",
                            reason=status.error_message or "Unknown failure",
                            consecutive_failures=self._consecutive_failures,
                            msg=f"Health check failed: {status.error_message or 'Unknown'}",
                        )
                        self._last_warning_time = current_time

                    # Trigger reconnection if threshold reached (AC #6)
                    if self._consecutive_failures >= 3:
                        LOGGER.warning(
                            "health.reconnect_trigger",
                            consecutive_failures=self._consecutive_failures,
                            msg="Health check failed 3 consecutive times, triggering reconnection",
                        )
                        # Trigger reconnection manager if available
                        if self._reconnection_manager is not None:
                            asyncio.create_task(
                                self._reconnection_manager.handle_connection_loss()
                            )

            except asyncio.CancelledError:
                # Graceful shutdown
                break
            except Exception as exc:  # pragma: no cover
                # Catch all exceptions to prevent loop crash
                LOGGER.error("health.loop_error", exception=str(exc))

            # Wait for next interval
            try:
                await asyncio.sleep(self._check_interval)
            except asyncio.CancelledError:
                break
