import logging
import time
from dataclasses import dataclass
from typing import List, Optional

from rate_limiter.config import RateLimiterConfig
from rate_limiter.core.algorithms import (
    FixedWindowAlgorithm,
    LeakyBucketAlgorithm,
    RateLimitAlgorithm,
    SlidingWindowAlgorithm,
    TokenBucketAlgorithm,
)
from rate_limiter.core.identity import IdentityResolver
from rate_limiter.core.rules import Algorithm, RateLimitRule
from rate_limiter.core.storage.backend import StorageBackend
from rate_limiter.core.storage.memory import MemoryStorageBackend
from rate_limiter.core.storage.redis import RedisStorageBackend
from rate_limiter.exceptions import RateLimitExceeded, StorageBackendError

logger = logging.getLogger(__name__)


@dataclass
class RateLimitInfo:
    """Rate limit information for headers."""

    limit: int
    remaining: int
    reset: int  # Unix timestamp when rate limit resets

    def to_headers(self) -> dict[str, str]:
        """Convert to HTTP headers."""
        return {
            "X-RateLimit-Limit": str(self.limit),
            "X-RateLimit-Remaining": str(self.remaining),
            "X-RateLimit-Reset": str(self.reset),
        }


class RateLimiter:

    def __init__(
        self,
        config: Optional[RateLimiterConfig] = None,
        storage: Optional[StorageBackend] = None,
        rules: Optional[List[RateLimitRule]] = None,
    ):
        """
        Initialize rate limiter.

        Args:
            config: Rate limiter configuration
            storage: Optional storage backend (overrides config)
            rules: Optional list of default rules
        """
        self.config = config or RateLimiterConfig.from_env()
        self._storage: Optional[StorageBackend] = storage
        self.default_rules = rules or []

        # Initialize algorithms
        self._algorithms = {
            Algorithm.FIXED_WINDOW: FixedWindowAlgorithm(),
            Algorithm.SLIDING_WINDOW: SlidingWindowAlgorithm(),
            Algorithm.TOKEN_BUCKET: TokenBucketAlgorithm(),
            Algorithm.LEAKY_BUCKET: LeakyBucketAlgorithm(),
        }

        # Initialize metrics if enabled
        self._metrics = None
        if self.config.enable_metrics:
            try:
                from rate_limiter.utils.metrics import get_metrics_collector

                # Use singleton pattern to avoid duplicate metric registration
                self._metrics = get_metrics_collector()
            except ImportError:
                logger.warning("Prometheus client not available, metrics disabled")

    async def _get_storage(self) -> StorageBackend:
        """Get or create storage backend."""
        if self._storage is None:
            if self.config.backend == "redis":
                self._storage = RedisStorageBackend(
                    redis_url=self.config.redis_url,
                    key_prefix=self.config.redis_key_prefix,
                    cluster_mode=self.config.redis_cluster_mode,
                )
            else:
                self._storage = MemoryStorageBackend()
        return self._storage

    def _get_algorithm(self, algorithm: Algorithm) -> RateLimitAlgorithm:
        """Get algorithm by enum."""
        if algorithm not in self._algorithms:
            raise ValueError(f"Unknown algorithm: {algorithm}")
        return self._algorithms[algorithm]

    async def check(
        self,
        identity: str,
        rules: Optional[List[RateLimitRule]] = None,
        endpoint: str = "",
        identity_resolver: Optional[IdentityResolver] = None,
    ) -> None:
        """
        Check if request should be allowed.

        Args:
            identity: Request identity (e.g., IP address)
            rules: List of rules to check (uses default_rules if not provided)
            endpoint: Endpoint identifier (for scoped rules)
            identity_resolver: Optional identity resolver (for custom resolution)

        Raises:
            RateLimitExceeded: If rate limit is exceeded
        """
        rules_to_check = rules or self.default_rules

        if not rules_to_check:
            # No rules, allow request
            return

        # Sort rules by priority (higher priority first)
        sorted_rules = sorted(rules_to_check, key=lambda r: r.priority, reverse=True)

        storage = await self._get_storage()

        for rule in sorted_rules:
            try:
                algorithm_impl = self._get_algorithm(rule.algorithm)
                allowed, remaining, retry_after = await algorithm_impl.allow(
                    storage, identity, rule, endpoint
                )

                # Record metrics
                if self._metrics:
                    self._metrics.record_request(rule.key, allowed)

                # Log request
                if self.config.enable_logging:
                    logger.info(
                        f"Rate limit check: identity={identity}, rule={rule.key}, "
                        f"allowed={allowed}, remaining={remaining}, retry_after={retry_after}"
                    )

                if not allowed:
                    raise RateLimitExceeded(
                        retry_after=retry_after,
                        limit=rule.limit,
                        window=rule.window,
                    )
            except StorageBackendError as e:
                logger.error(f"Storage backend error: {e}")
                if self.config.fail_closed:
                    # Fail closed: deny request on backend failure
                    raise RateLimitExceeded(
                        retry_after=60,
                        limit=0,
                        window=60,
                        message="Rate limiter backend unavailable",
                    )
                # Fail open: allow request on backend failure
                logger.warning("Allowing request due to backend failure (fail_open mode)")

    async def check_with_info(
        self,
        identity: str,
        rules: Optional[List[RateLimitRule]] = None,
        endpoint: str = "",
        identity_resolver: Optional[IdentityResolver] = None,
    ) -> tuple[Optional[RateLimitInfo], Optional[RateLimitExceeded]]:
        """
        Check rate limit and return info. Returns (info, None) if allowed, (None, exception) if not.

        Args:
            identity: Request identity (e.g., IP address)
            rules: List of rules to check (uses default_rules if not provided)
            endpoint: Endpoint identifier (for scoped rules)
            identity_resolver: Optional identity resolver (for custom resolution)

        Returns:
            Tuple of (RateLimitInfo or None, RateLimitExceeded or None)
        """
        rules_to_check = rules or self.default_rules

        if not rules_to_check:
            return None, None

        # Sort rules by priority (higher priority first)
        sorted_rules = sorted(rules_to_check, key=lambda r: r.priority, reverse=True)

        storage = await self._get_storage()
        current_time = int(time.time())

        for rule in sorted_rules:
            try:
                algorithm_impl = self._get_algorithm(rule.algorithm)
                allowed, remaining, retry_after = await algorithm_impl.allow(
                    storage, identity, rule, endpoint
                )

                # Calculate reset timestamp
                reset_timestamp = current_time + retry_after
                rate_limit_info = RateLimitInfo(
                    limit=rule.limit,
                    remaining=remaining,
                    reset=reset_timestamp,
                )

                # Record metrics
                if self._metrics:
                    self._metrics.record_request(rule.key, allowed)

                # Log request
                if self.config.enable_logging:
                    logger.info(
                        f"Rate limit check: identity={identity}, rule={rule.key}, "
                        f"allowed={allowed}, remaining={remaining}, retry_after={retry_after}"
                    )

                if not allowed:
                    exception = RateLimitExceeded(
                        retry_after=retry_after,
                        limit=rule.limit,
                        window=rule.window,
                    )
                    return None, exception

                # Return info for the first (highest priority) rule that passes
                return rate_limit_info, None

            except StorageBackendError as e:
                logger.error(f"Storage backend error: {e}")
                if self.config.fail_closed:
                    # Fail closed: deny request on backend failure
                    exception = RateLimitExceeded(
                        retry_after=60,
                        limit=0,
                        window=60,
                        message="Rate limiter backend unavailable",
                    )
                    return None, exception
                # Fail open: allow request on backend failure
                logger.warning("Allowing request due to backend failure (fail_open mode)")
                continue

        return None, None

    async def get_rate_limit_info(
        self,
        identity: str,
        rules: Optional[List[RateLimitRule]] = None,
        endpoint: str = "",
    ) -> Optional[RateLimitInfo]:
        """
        Get rate limit information without raising exceptions.
        Note: This calls check_with_info but doesn't increment counters.

        Args:
            identity: Request identity (e.g., IP address)
            rules: List of rules to check (uses default_rules if not provided)
            endpoint: Endpoint identifier (for scoped rules)

        Returns:
            RateLimitInfo if rules exist, None otherwise
        """
        # Use check_with_info but ignore the exception part
        info, _ = await self.check_with_info(identity=identity, rules=rules, endpoint=endpoint)
        return info

    async def close(self) -> None:
        """Close rate limiter and cleanup resources."""
        if self._storage:
            await self._storage.close()
            self._storage = None
