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

"""Vhost validation middleware for topology operations (Story 3.11)."""

import asyncio
import functools
import inspect
import os
import time
from collections.abc import Callable
from typing import Any, TypeVar, cast

from config.logging import get_logger
from rabbitmq_mcp_connection.connection.exceptions import VHostNotFoundError

logger = get_logger(__name__)

# Cache TTL in seconds (AC #4) - configurable via environment variable
CACHE_TTL = int(os.getenv("VHOST_CACHE_TTL_SECONDS", "300"))  # 5 minutes default

# Validation timeout in seconds (AC #3 - should complete in <50ms)
VALIDATION_TIMEOUT = 10.0  # Generous timeout for network calls

F = TypeVar("F", bound=Callable[..., Any])


class VhostValidator:
    """Validates vhost existence before topology operations (AC #1-6).

    Implements caching with 5-minute TTL to avoid repeated API calls.
    Thread-safe for concurrent operations using asyncio locks.
    """

    def __init__(self, http_client: Any) -> None:
        """Initialize VhostValidator.

        Args:
            http_client: HTTPClientManager instance for API calls
        """
        self.http_client = http_client
        self._cache: dict[str, float] = {}  # vhost -> timestamp
        self._lock = asyncio.Lock()
        self._all_vhosts: list[str] | None = None
        self._cache_timestamp: float = 0.0

    async def validate_vhost(
        self, vhost: str, correlation_id: str | None = None
    ) -> None:
        """Validate vhost existence (AC #1, #2, #3, #4, #5).

        Args:
            vhost: Virtual host name to validate
            correlation_id: Optional correlation ID for audit trail

        Raises:
            VHostNotFoundError: If vhost does not exist (AC #2)
            asyncio.TimeoutError: If validation exceeds timeout (AC #3)
        """
        validation_start = time.perf_counter()

        try:
            # Apply timeout to ensure fast failure (AC #3)
            async with asyncio.timeout(VALIDATION_TIMEOUT):
                async with self._lock:
                    current_time = time.time()

                    # Check if vhost is in cache and not expired (AC #4)
                    if vhost in self._cache:
                        cached_time = self._cache[vhost]
                        if current_time - cached_time < CACHE_TTL:
                            duration_ms = (
                                time.perf_counter() - validation_start
                            ) * 1000
                            logger.debug(
                                "vhost.validation.cache_hit",
                                vhost=vhost,
                                correlation_id=correlation_id,
                                duration_ms=duration_ms,
                            )
                            return

                    # Cache miss or expired - fetch from API (AC #5)
                    logger.debug(
                        "vhost.validation.cache_miss",
                        vhost=vhost,
                        correlation_id=correlation_id,
                    )

                    # Fetch all vhosts if cache is empty or expired
                    if (
                        self._all_vhosts is None
                        or current_time - self._cache_timestamp >= CACHE_TTL
                    ):
                        await self._fetch_vhosts(correlation_id)

                    # Validate vhost exists (must have vhost list at this point)
                    if self._all_vhosts is None:
                        raise RuntimeError("Failed to fetch vhost list")

                    if vhost not in self._all_vhosts:
                        # Raise error with list of available vhosts (AC #2)
                        available = ", ".join(sorted(self._all_vhosts))
                        error_msg = (
                            f"Virtual host '{vhost}' does not exist. "
                            f"Available vhosts: [{available}]"
                        )
                        if correlation_id:
                            error_msg += f" (correlation_id: {correlation_id})"

                        logger.error(
                            "vhost.validation.failed",
                            vhost=vhost,
                            available_vhosts=self._all_vhosts,
                            correlation_id=correlation_id,
                        )
                        raise VHostNotFoundError(error_msg)

                    # Update cache for valid vhost (AC #4)
                    self._cache[vhost] = current_time
                    duration_ms = (time.perf_counter() - validation_start) * 1000
                    logger.debug(
                        "vhost.validation.success",
                        vhost=vhost,
                        correlation_id=correlation_id,
                        duration_ms=duration_ms,
                    )
        except TimeoutError:
            duration_ms = (time.perf_counter() - validation_start) * 1000
            logger.error(
                "vhost.validation.timeout",
                vhost=vhost,
                correlation_id=correlation_id,
                duration_ms=duration_ms,
                timeout_seconds=VALIDATION_TIMEOUT,
            )
            raise

    async def _fetch_vhosts(self, correlation_id: str | None = None) -> None:
        """Fetch all vhosts from RabbitMQ Management API (AC #5).

        Args:
            correlation_id: Optional correlation ID for audit trail

        Raises:
            Exception: If API call fails
        """
        fetch_start = time.perf_counter()
        try:
            logger.debug(
                "vhost.fetch.start",
                correlation_id=correlation_id,
            )

            # GET /api/vhosts (AC #5)
            response = await self.http_client.request(
                method="GET",
                endpoint="/vhosts",
                correlation_id=correlation_id,
            )

            # Extract vhost names from response
            vhosts_data = response if isinstance(response, list) else []
            self._all_vhosts = [vhost["name"] for vhost in vhosts_data]
            self._cache_timestamp = time.time()

            duration_ms = (time.perf_counter() - fetch_start) * 1000
            logger.info(
                "vhost.fetch.success",
                vhost_count=len(self._all_vhosts),
                vhosts=self._all_vhosts,
                correlation_id=correlation_id,
                duration_ms=duration_ms,
            )

        except Exception as e:
            duration_ms = (time.perf_counter() - fetch_start) * 1000
            logger.error(
                "vhost.fetch.failed",
                error=str(e),
                correlation_id=correlation_id,
                duration_ms=duration_ms,
            )
            # Re-raise to propagate error
            raise

    def invalidate_cache(self) -> None:
        """Invalidate vhost cache to force refresh (AC #6).

        Should be called after vhost create/delete operations to ensure
        the cache reflects the current state of vhosts.

        Note: Vhost create/delete operations are not yet implemented in the codebase
        (future Epic 4+). This method is provided for future integration.
        When those operations are implemented, they should call this method after
        successful vhost creation or deletion.

        Example future usage:
            # In CreateVhostOperation.execute():
            # ... create vhost via API ...
            # Invalidate validator cache on all HTTP clients
            if hasattr(self.http_client, '_vhost_validator'):
                self.http_client._vhost_validator.invalidate_cache()
        """
        cache_size = len(self._cache)
        logger.info(
            "vhost.cache.invalidated",
            cached_vhosts_count=cache_size,
            had_vhost_list=self._all_vhosts is not None,
        )
        self._cache.clear()
        self._all_vhosts = None
        self._cache_timestamp = 0.0


def validate_vhost(func: F) -> F:
    """Decorator to validate vhost parameter before executing function (AC #1).

    Extracts vhost from function kwargs or from request object and validates it before execution.
    Works with async functions (sync functions not supported).

    Args:
        func: Function to decorate (must have vhost parameter or request with vhost)

    Returns:
        Decorated function

    Raises:
        ValueError: If vhost parameter is missing or instance configuration is invalid
        VHostNotFoundError: If vhost does not exist
        asyncio.TimeoutError: If validation exceeds timeout

    Example:
        >>> @validate_vhost
        ... async def list_queues(self, vhost: str, correlation_id: str | None = None):
        ...     # vhost is validated before this code runs
        ...     return await self.http_client.request("GET", f"/queues/{vhost}")
    """

    @functools.wraps(func)
    async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
        # Extract vhost from kwargs or request object
        vhost = kwargs.get("vhost")

        # If not found directly, check if there's a request object with vhost
        if vhost is None:
            request = kwargs.get("request")
            # If not in kwargs, check if first positional arg (after self) is a request
            if request is None and len(args) > 1:
                potential_request = args[1]
                if hasattr(potential_request, "vhost"):
                    request = potential_request

            if request is not None and hasattr(request, "vhost"):
                vhost = request.vhost

        if vhost is None:
            # Get function name for better error message
            func_name = func.__name__ if hasattr(func, "__name__") else "unknown"
            raise ValueError(
                f"vhost parameter is required for {func_name}. "
                "Provide it as a keyword argument or in the request object."
            )

        # Get validator instance from self.http_client
        if not args:
            raise ValueError("Decorated method must be called on an instance")

        instance = args[0]
        if not hasattr(instance, "http_client"):
            raise ValueError(
                f"Instance {type(instance).__name__} must have http_client attribute"
            )

        http_client = instance.http_client
        if not hasattr(http_client, "_vhost_validator"):
            # Create validator instance on first use
            http_client._vhost_validator = VhostValidator(http_client)

        validator: VhostValidator = http_client._vhost_validator

        # Get correlation_id if present
        correlation_id = kwargs.get("correlation_id")

        # Validate vhost
        await validator.validate_vhost(vhost, correlation_id)

        # Execute original function
        return await func(*args, **kwargs)

    @functools.wraps(func)
    def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
        # For sync functions, we need to handle differently
        # This is a fallback but topology operations are async
        raise NotImplementedError(
            "Sync functions not supported by validate_vhost decorator"
        )

    # Return appropriate wrapper based on function type
    if inspect.iscoroutinefunction(func):
        return cast(F, async_wrapper)
    else:
        return cast(F, sync_wrapper)
