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

import os
from pathlib import Path
from typing import Any

import yaml
from dotenv import load_dotenv
from pydantic import SecretStr, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

load_dotenv()


class Settings(BaseSettings):
    # Legacy generic connection fields (maintained for backward compatibility)
    host: str = ""
    port: int = 15672
    user: str = ""
    password: str = ""
    vhost: str = "/"
    use_tls: bool = False
    crud_timeout: int = 5
    list_timeout: int = 30
    search_timeout: float = 0.1

    # AMQP Connection Configuration (Story 2.1 - AC #2)
    amqp_host: str = "localhost"
    amqp_port: int = 5672
    amqp_user: str = "guest"
    amqp_password: SecretStr = SecretStr("guest")
    amqp_vhost: str = "/"
    amqp_use_tls: bool = False
    amqp_verify_cert: bool = True
    amqp_ca_bundle: str | None = None

    # HTTP Management API Configuration (Story 2.1 - AC #2)
    http_host: str = "localhost"
    http_port: int = 15672
    http_user: str = "guest"
    http_password: SecretStr = SecretStr("guest")
    http_use_tls: bool = False
    http_verify_cert: bool = True
    http_ca_bundle: str | None = None
    http_timeout: int = 30  # AC #5 Story 2.3
    max_http_connections: int = 5  # AC #7 Story 2.3

    # General Configuration
    config_file: str | None = None
    health_check_interval: int = 30  # Story 2.4 AC #4
    health_check_timeout: int = 1  # Story 2.4 AC #3 (<1 second)
    pool_health_check_interval: int = 30  # Story 2.6 AC #10 (proactive health checks)
    reconnect_max_delay: int = 60  # Story 2.5 AC #1 (max backoff delay)
    reconnect_reset_after: int = (
        300  # Story 2.5 AC #9 (backoff reset after stable connection)
    )

    # MCP Server configuration
    # Note: API version cannot be changed at runtime - server restart required
    rabbitmq_api_version: str = "3.13"
    log_level: str = "INFO"
    log_output_dir: str = "logs"

    @property
    def api_version(self) -> str:
        """Return API version (alias for rabbitmq_api_version)."""
        return self.rabbitmq_api_version

    @property
    def operations_registry_path(self) -> str:
        """Return version-specific operations registry path."""
        return str(self.get_operations_path())

    @property
    def embeddings_path(self) -> str:
        """Return version-specific embeddings path."""
        return str(self.get_embeddings_path())

    # HTTP Client configuration for Management API (Story 1.10)
    rabbitmq_base_url: str = "http://localhost:15672"
    rabbitmq_username: str = "guest"
    rabbitmq_password: SecretStr = SecretStr("guest")
    rabbitmq_verify_ssl: bool = True
    rabbitmq_timeout: int = 30
    rabbitmq_pool_size: int = 5

    model_config = SettingsConfigDict(
        env_file=".env", env_file_encoding="utf-8", env_prefix=""
    )

    @field_validator("rabbitmq_api_version")
    @classmethod
    def validate_api_version(cls, v: str) -> str:
        """Validate API version is one of the supported versions."""
        supported_versions = ["3.11", "3.12", "3.13"]
        if v not in supported_versions:
            raise ValueError(
                f"API version '{v}' not supported. Supported: 3.11, 3.12, 3.13. "
                "Check available OpenAPI files in data/ directory."
            )
        env_status = "set" if os.getenv("RABBITMQ_API_VERSION") else "default"
        print(f"Using RabbitMQ API version: {v} (env var: {env_status})")
        return v

    @field_validator("amqp_port", "http_port")
    @classmethod
    def validate_port(cls, v: int) -> int:
        """Validate port is in valid range (AC #5 Story 2.1)."""
        if not 1 <= v <= 65535:
            raise ValueError(f"Port must be between 1 and 65535, got {v}")
        return v

    @field_validator("http_timeout")
    @classmethod
    def validate_http_timeout(cls, v: int) -> int:
        """Validate HTTP timeout is in valid range (AC #5 Story 2.3)."""
        if not 1 <= v <= 300:
            raise ValueError(f"HTTP timeout must be between 1 and 300 seconds, got {v}")
        return v

    @field_validator("max_http_connections")
    @classmethod
    def validate_max_http_connections(cls, v: int) -> int:
        """Validate max HTTP connections is in valid range (AC #7 Story 2.3)."""
        if not 1 <= v <= 20:
            raise ValueError(f"Max HTTP connections must be between 1 and 20, got {v}")
        return v

    @field_validator("health_check_interval")
    @classmethod
    def validate_health_check_interval(cls, v: int) -> int:
        """Validate health check interval is in valid range (AC #4 Story 2.4)."""
        if not 5 <= v <= 300:
            raise ValueError(
                f"Health check interval must be between 5 and 300 seconds, got {v}"
            )
        return v

    @field_validator("health_check_timeout")
    @classmethod
    def validate_health_check_timeout(cls, v: int) -> int:
        """Validate health check timeout is in valid range (AC #3 Story 2.4)."""
        if not 1 <= v <= 10:
            raise ValueError(
                f"Health check timeout must be between 1 and 10 seconds, got {v}"
            )
        return v

    @field_validator("pool_health_check_interval")
    @classmethod
    def validate_pool_health_check_interval(cls, v: int) -> int:
        """Validate pool health check interval is in valid range (AC #10 Story 2.6)."""
        if not 5 <= v <= 300:
            raise ValueError(
                f"Pool health check interval must be between 5 and 300 seconds, got {v}"
            )
        return v

    @field_validator("reconnect_max_delay")
    @classmethod
    def validate_reconnect_max_delay(cls, v: int) -> int:
        """Validate reconnect max delay is in valid range (AC #1 Story 2.5)."""
        if not 10 <= v <= 300:
            raise ValueError(
                f"Reconnect max delay must be between 10 and 300 seconds, got {v}"
            )
        return v

    @field_validator("reconnect_reset_after")
    @classmethod
    def validate_reconnect_reset_after(cls, v: int) -> int:
        """Validate reconnect reset duration is in valid range (AC #9 Story 2.5)."""
        if not 60 <= v <= 3600:
            raise ValueError(
                f"Reconnect reset after must be between 60 and 3600 seconds, got {v}"
            )
        return v

    @field_validator("amqp_vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (AC #5 Story 2.1)."""
        if not v.startswith("/"):
            raise ValueError(f"vhost must start with '/', got '{v}'")
        return v

    @field_validator("amqp_ca_bundle", "http_ca_bundle")
    @classmethod
    def validate_ca_bundle(cls, v: str | None) -> str | None:
        """Validate TLS certificate path exists if specified (AC #5 Story 2.1)."""
        if v is not None and v != "" and not Path(v).exists():
            raise ValueError(f"CA bundle file not found: {v}")
        return v

    @model_validator(mode="after")
    def validate_tls_consistency(self) -> Settings:
        """Check TLS settings consistency (AC #5 Story 2.1)."""
        if self.amqp_use_tls and not self.amqp_verify_cert:
            print("Warning: AMQP TLS enabled but certificate verification disabled")
        if self.http_use_tls and not self.http_verify_cert:
            print("Warning: HTTP TLS enabled but certificate verification disabled")
        return self

    def get_api_version(self) -> str:
        """Return validated API version string."""
        return self.rabbitmq_api_version

    def get_openapi_path(self) -> Path:
        """Return Path to version-specific OpenAPI file."""
        return Path(f"data/openapi-{self.rabbitmq_api_version}.yaml")

    def get_operations_path(self) -> Path:
        """Return Path to version-specific operations registry."""
        return Path(f"data/operations-{self.rabbitmq_api_version}.json")

    def get_embeddings_path(self) -> Path:
        """Return Path to version-specific embeddings file."""
        return Path(f"data/embeddings-{self.rabbitmq_api_version}.json")

    def get_schemas_module_name(self) -> str:
        """Return module name for dynamic schema import."""
        return f"schemas_{self.rabbitmq_api_version.replace('.', '_')}"

    def validate_openapi_exists(self) -> None:
        """Validate OpenAPI file exists at startup."""
        openapi_path = self.get_openapi_path()
        if not openapi_path.exists():
            raise FileNotFoundError(
                f"OpenAPI file not found: {openapi_path}. "
                "Ensure file exists before running generation."
            )

    def validate_http_settings(self) -> None:
        """Validate HTTP client settings (AC #4 Story 1.10)."""
        assert self.rabbitmq_timeout > 0, "rabbitmq_timeout must be greater than 0"
        assert self.rabbitmq_pool_size >= 1, "rabbitmq_pool_size must be at least 1"
        assert self.rabbitmq_base_url, "rabbitmq_base_url is required"
        assert self.rabbitmq_base_url.startswith(
            ("http://", "https://")
        ), "rabbitmq_base_url must be a valid URL"

    @classmethod
    def from_yaml(cls, path: str = "config/config.yaml") -> Settings:
        if not os.path.exists(path):
            # Allow BaseSettings to resolve values from environment variables when YAML is absent.
            return cls(**{})

        with open(path, encoding="utf-8") as f:
            raw = yaml.safe_load(f) or {}

        # Support nested rabbitmq configuration blocks while preserving direct mapping usage.
        data = raw.get("rabbitmq", raw)

        normalized: dict[str, Any] = {}
        for key, value in data.items():
            if isinstance(value, str):
                normalized[key] = os.path.expandvars(value)
            else:
                normalized[key] = value

        return cls(**normalized)

    def validate_timeouts(self) -> None:
        assert (
            1 <= self.crud_timeout <= 60
        ), "crud_timeout must be between 1 and 60 seconds"
        assert (
            1 <= self.list_timeout <= 60
        ), "list_timeout must be between 1 and 60 seconds"
        assert (
            0.1 <= self.search_timeout <= 60
        ), "search_timeout must be between 0.1 and 60 seconds"
        assert self.host and self.port, "host and port are required"

        # Validate HTTP client settings (AC #4 Story 1.10)
        self.validate_http_settings()
