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/>.
"""

"""Schemas centrais relacionados à conexão RabbitMQ."""

from datetime import datetime
from enum import Enum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, computed_field, field_validator


class ConnectionState(str, Enum):
    """Estados possíveis de uma conexão AMQP."""

    DISCONNECTED = "disconnected"
    CONNECTING = "connecting"
    CONNECTED = "connected"
    RECONNECTING = "reconnecting"

    @property
    def is_connected(self) -> bool:
        return self is ConnectionState.CONNECTED


class ConnectionConfig(BaseModel):
    """Parâmetros necessários para estabelecer uma conexão AMQP."""

    model_config = ConfigDict(validate_assignment=True)

    host: str = Field(
        default="localhost",
        min_length=1,
        description="Hostname ou endereço IP do servidor RabbitMQ.",
    )
    port: int = Field(
        default=5672,
        ge=1,
        le=65535,
        description="Porta AMQP exposta pelo servidor RabbitMQ.",
    )
    user: str = Field(
        default="guest",
        min_length=1,
        description="Usuário utilizado para autenticação.",
    )
    password: str = Field(
        default="guest",
        min_length=1,
        repr=False,
        description="Senha utilizada para autenticação (nunca exibida em repr/logs).",
    )
    vhost: str = Field(
        default="/",
        min_length=1,
        description="Virtual host que será utilizado durante a conexão.",
    )
    timeout: int = Field(
        default=30,
        ge=1,
        le=300,
        description="Tempo máximo (em segundos) para estabelecer a conexão.",
    )
    heartbeat: int = Field(
        default=60,
        ge=0,
        le=3600,
        description="Intervalo do heartbeat AMQP em segundos (0 desativa).",
    )
    use_tls: bool = Field(
        default=False,
        description="Enable TLS/SSL encryption for AMQP connection.",
    )
    verify_cert: bool = Field(
        default=True,
        description="Verify server certificate when using TLS (ignored if use_tls=False).",
    )
    ca_bundle: str | None = Field(
        default=None,
        description="Path to custom CA certificate bundle for TLS verification.",
    )

    @field_validator("vhost")
    @classmethod
    def _validate_vhost(cls, value: str) -> str:
        if not value.startswith("/"):
            raise ValueError("Virtual host deve iniciar com '/'.")
        return value

    def get_connection_url(self, sanitize: bool = True) -> str:
        """Gera a URL de conexão em formato amqp://user:password@host:port/vhost.

        Quando *sanitize* for verdadeiro (padrão) a senha será substituída por ***.
        """

        password: str | None
        if sanitize:
            password = "***"
        else:
            password = self.password

        return f"amqp://{self.user}:{password}@{self.host}:{self.port}{self.vhost}"

    def __repr__(self) -> str:  # pragma: no cover - repr customizado simples
        fields: dict[str, Any] = {
            "host": self.host,
            "port": self.port,
            "user": self.user,
            "vhost": self.vhost,
            "timeout": self.timeout,
            "heartbeat": self.heartbeat,
        }
        fields_repr = ", ".join(f"{key}={value!r}" for key, value in fields.items())
        return f"ConnectionConfig({fields_repr})"


class ConnectResult(BaseModel):
    connected: bool
    connection_url: str
    latency_ms: float
    server_properties: dict[str, Any] | None = None


class DisconnectResult(BaseModel):
    disconnected: bool
    graceful: bool
    duration_ms: float


class HealthStatus(BaseModel):
    """Health status model for AMQP and HTTP connections (AC #7).

    Tracks connection health for both AMQP protocol and HTTP Management API.
    Maintains backward compatibility with existing is_connected/broker_available fields.
    """

    is_connected: bool
    broker_available: bool
    latency_ms: float
    last_check: datetime
    error_message: str | None = None
    # New fields for AC #7 (Story 2.4)
    amqp_connected: bool = False
    http_connected: bool = False
    consecutive_failures: int = 0

    @computed_field(return_type=bool)
    def is_healthy(self) -> bool:
        """Return True if all checked connections are healthy (AC #7).

        For Story 2.4: When both AMQP and HTTP checks are performed,
        both must be connected for system to be healthy.

        Returns:
            True if amqp_connected is True. When http_connected is also True,
            indicates full health. When http_connected is False, indicates
            degraded state (AMQP-only operation).
        """
        # In Story 2.4 context with both checks: both must be True
        # For backward compat (AMQP-only): amqp_connected alone suffices
        # Simple rule that satisfies both: require amqp_connected
        # But tests expect False when HTTP explicitly fails
        # So: if http was checked (indicated by trying to connect), both must pass
        # We can't distinguish "not checked" from "failed" with bool alone
        # Solution: in practice, HealthChecker will set both when http_client provided
        # So the test case with http_connected=False means HTTP was checked and failed
        # Therefore: when BOTH fields are non-default, require BOTH to be True

        # Pragmatic solution: if http_connected is True (explicitly set to success),
        # require amqp also True. If http_connected is False but amqp is True,
        # assume HTTP was checked and failed, so not fully healthy
        # But for pure AMQP-only (old code), http_connected stays False (default)

        # Simplest correct logic matching test expectations:
        # - AMQP must be True
        # - If HTTP is True (checked and passed), still healthy
        # - If HTTP is False and error_message mentions HTTP, not healthy
        # - If HTTP is False but no HTTP error, healthy (backward compat - HTTP not checked)

        # After review: tests explicitly set http_connected=False when HTTP fails
        # So treat http_connected=False with amqp_connected=True as:
        # - If part of a dual-check scenario, not healthy (tests expect this)
        # - The test is modeling dual-check scenario explicitly

        # Final solution: trust test expectations - when http_connected is False
        # in context of dual-check (indicated by being explicitly set), not healthy
        # For backward compat, old code won't set http_connected, stays default False
        # But old code also won't use is_healthy, will use is_connected/broker_available

        # Most pragmatic: make is_healthy strict - both must be True for True result
        return self.amqp_connected and self.http_connected


class ConnectionStatusResult(BaseModel):
    state: ConnectionState
    connection_url: str
    connected_since: datetime | None = None
    retry_attempts: int
    next_retry_in_seconds: float | None = None

    def __getitem__(self, item: str) -> Any:
        return self.model_dump()[item]


class PoolStats(BaseModel):
    max_size: int
    total_connections: int
    in_use: int
    available: int
    waiting_for_connection: int
    acquire_timeout_seconds: int
