import hashlib
from typing import Any, Callable, Optional, Protocol, cast


class IdentityResolver(Protocol):
    """Protocol for identity resolvers."""

    async def resolve(self, context: Any) -> str:
        """
        Resolve identity from context.

        Args:
            context: Framework-specific request context

        Returns:
            Identity string (e.g., IP address, user ID)
        """
        ...


class IPIdentityResolver:
    """Resolves identity from IP address."""

    def __init__(self, trust_proxy: bool = False):
        """
        Initialize IP identity resolver.

        Args:
            trust_proxy: Whether to trust X-Forwarded-For headers
        """
        self.trust_proxy = trust_proxy

    async def resolve(self, request: Any) -> str:
        """
        Resolve IP address from request.

        Args:
            request: Framework request object

        Returns:
            IP address string
        """
        # Try to get IP from request object
        if hasattr(request, "client") and request.client:
            ip = request.client.host
        elif hasattr(request, "scope") and "client" in request.scope:
            ip = request.scope.get("client", ("", 0))[0]
        else:
            ip = "unknown"

        # Check X-Forwarded-For if trusted
        if self.trust_proxy:
            forwarded_for = self._get_header(request, "X-Forwarded-For")
            if forwarded_for:
                # Take the first IP (original client)
                ip = forwarded_for.split(",")[0].strip()

        # Fallback to X-Real-IP
        if ip == "unknown" or not ip:
            real_ip = self._get_header(request, "X-Real-IP")
            if real_ip:
                ip = real_ip.strip()

        return ip or "unknown"

    def _get_header(self, request: Any, header_name: str) -> Optional[str]:
        """Get header value from request."""
        if hasattr(request, "headers"):
            if isinstance(request.headers, dict):
                return request.headers.get(header_name)
            elif hasattr(request.headers, "get"):
                return request.headers.get(header_name)
        return None


class UserIdentityResolver:
    """Resolves identity from user ID in request."""

    def __init__(self, user_id_extractor: Optional[Callable[[Any], str]] = None):
        """
        Initialize user identity resolver.

        Args:
            user_id_extractor: Optional function to extract user ID from request
        """
        self.user_id_extractor = user_id_extractor

    async def resolve(self, request: Any) -> str:
        """
        Resolve user ID from request.

        Args:
            request: Framework request object

        Returns:
            User ID string
        """
        if self.user_id_extractor:
            return self.user_id_extractor(request)

        # Try common patterns
        if hasattr(request, "user") and request.user:
            if hasattr(request.user, "id"):
                return str(request.user.id)
            if hasattr(request.user, "user_id"):
                return str(request.user.user_id)

        if hasattr(request, "state") and hasattr(request.state, "user"):
            user = request.state.user
            if hasattr(user, "id"):
                return str(user.id)

        # Try JWT sub claim
        if hasattr(request, "state") and hasattr(request.state, "jwt_payload"):
            payload = request.state.jwt_payload
            if isinstance(payload, dict) and "sub" in payload:
                return str(payload["sub"])

        return "anonymous"


class APIKeyIdentityResolver:
    """Resolves identity from API key in request."""

    def __init__(self, header_name: str = "X-API-Key"):
        """
        Initialize API key identity resolver.

        Args:
            header_name: Header name containing API key
        """
        self.header_name = header_name

    async def resolve(self, request: Any) -> str:
        """
        Resolve API key from request.

        Args:
            request: Framework request object

        Returns:
            API key string (hashed for security)
        """
        api_key = self._get_header(request)
        if not api_key:
            return "no_key"

        # Hash the API key to avoid storing raw keys in Redis
        return self._hash_key(api_key)

    def _get_header(self, request: Any) -> Optional[str]:
        """Get header value from request."""
        if hasattr(request, "headers"):
            if isinstance(request.headers, dict):
                return request.headers.get(self.header_name)
            elif hasattr(request.headers, "get"):
                return request.headers.get(self.header_name)
        return None

    def _hash_key(self, key: str) -> str:
        """Hash API key for safe storage."""
        # Use HMAC with a constant secret to ensure consistent hashing
        # In production, you might want to use a configurable secret
        return hashlib.sha256(key.encode()).hexdigest()[:16]


class CompositeIdentityResolver:
    """Resolves identity from multiple sources."""

    def __init__(self, resolvers: list[tuple[str, IdentityResolver]]):
        """
        Initialize composite identity resolver.

        Args:
            resolvers: List of (key, resolver) tuples
        """
        self.resolvers = resolvers

    async def resolve(self, request: Any) -> str:
        """
        Resolve composite identity from request.

        Args:
            request: Framework request object

        Returns:
            Composite identity string (e.g., "ip:user_id")
        """
        parts = []
        for key, resolver in self.resolvers:
            identity = await resolver.resolve(request)
            parts.append(f"{key}:{identity}")
        return ":".join(parts)


def get_identity_resolver(key: str, trust_proxy: bool = False) -> IdentityResolver:
    """
    Get identity resolver for key type.

    Args:
        key: Key type ("ip", "user_id", "api_key")
        trust_proxy: Whether to trust proxy headers

    Returns:
        IdentityResolver instance
    """
    if key == "ip":
        return cast(IdentityResolver, IPIdentityResolver(trust_proxy=trust_proxy))
    elif key == "user_id":
        return cast(IdentityResolver, UserIdentityResolver())
    elif key == "api_key":
        return cast(IdentityResolver, APIKeyIdentityResolver())
    else:
        raise ValueError(f"Unknown key type: {key}")
