from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import httpx


class Validator(ABC):
    """
    Abstract base class for API key validation strategies.

    This interface follows the Strategy pattern, allowing different
    validation approaches for various API providers. Each validator
    implements provider-specific logic to verify key validity.

    The ``_safe_fetch`` helper evaluates real HTTP status codes so that
    rate-limit responses (429) are correctly interpreted as proof that
    the key exists rather than a validation failure.

    Attributes:
        name: Human-readable name of the validator

    Example:
        class CustomValidator(Validator):
            async def validate(self, client, key):
                return await self._safe_fetch(
                    client,
                    "https://api.example.com/verify",
                    headers={"Authorization": f"Bearer {key}"},
                )
    """

    name: str = "base"

    @abstractmethod
    async def validate(self, client: "httpx.AsyncClient", key: str) -> bool:
        """
        Validate an API key against the provider's authentication endpoint.

        Args:
            client: An async HTTP client for making requests
            key: The API key to validate

        Returns:
            True if the key is valid, False otherwise
        """
        pass

    async def _safe_fetch(
        self,
        client: "httpx.AsyncClient",
        url: str,
        headers: dict[str, str] | None = None,
        valid_codes: tuple[int, ...] = (200, 429),
    ) -> bool:
        """
        Resilient HTTP wrapper for key validation.

        Instead of treating every non-200 or exception as "invalid key",
        this method checks the actual status code.  A 200 *and* a 429
        both prove that the key authenticated successfully (429 simply
        means the key hit its rate limit).

        Subclasses can override ``valid_codes`` to add provider-specific
        heuristics (e.g. Perplexity treats 400 as valid auth).

        Args:
            client: The async HTTP client
            url: Endpoint to probe
            headers: Optional request headers
            valid_codes: HTTP status codes that confirm the key exists

        Returns:
            True if the response status is in ``valid_codes``, False on
            any other status or on network/timeout errors.
        """
        try:
            response = await client.get(url, headers=headers or {})
            return response.status_code in valid_codes
        except Exception:
            return False

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}>"
