"""Input validation utilities for SDK methods.

This module provides minimal validation helpers that catch obvious user errors
before making network requests. Validation is kept lightweight - the server
remains the authoritative validator for business logic constraints.

What to validate:
    - Required parameters are not None/empty
    - Basic type correctness (list vs string)
    - Obvious bounds that prevent network round-trips (e.g., top_n > 0)

What NOT to validate:
    - Model names, dimension ranges, format patterns (server decides)
    - Complex business logic constraints
    - Exhaustive input checking
"""

from __future__ import annotations

import json

from typing import TYPE_CHECKING, Any, Protocol, TypeVar

import msgspec


if TYPE_CHECKING:
    import httpx


T = TypeVar("T")
ResponseT_co = TypeVar("ResponseT_co", covariant=True)


class ResponseParser(Protocol[ResponseT_co]):
    """Protocol for response parsers with from_response method."""

    def from_response(self, data: dict[str, Any]) -> ResponseT_co:
        """Parse response dict to typed model."""
        ...


def require_non_empty(name: str, value: str | list[Any] | None) -> None:
    """Raise ValueError if value is None, empty string, or empty list.

    Args:
        name: Parameter name for error message
        value: Value to check

    Raises:
        ValueError: If value is None, empty string, or empty list

    Example:
        >>> require_non_empty("model", "multilingual-e5-large")  # OK
        >>> require_non_empty("texts", ["Hello"])  # OK
        >>> require_non_empty("model", "")  # Raises ValueError
        >>> require_non_empty("texts", [])  # Raises ValueError
        >>> require_non_empty("query", None)  # Raises ValueError
    """
    if value is None:
        raise ValueError(f"{name} is required")

    if isinstance(value, str) and not value:
        raise ValueError(f"{name} must be a non-empty string")

    if isinstance(value, list) and not value:
        raise ValueError(f"{name} must be a non-empty list")


def require_in_range(name: str, value: int | None, min_val: int, max_val: int) -> None:
    """Raise ValueError if value is outside [min_val, max_val].

    None is always allowed (for optional parameters).

    Args:
        name: Parameter name for error message
        value: Value to check
        min_val: Minimum allowed value (inclusive)
        max_val: Maximum allowed value (inclusive)

    Raises:
        ValueError: If value is outside the specified range

    Example:
        >>> require_in_range("top_n", 5, 1, 10)  # OK
        >>> require_in_range("top_n", None, 1, 10)  # OK (None allowed)
        >>> require_in_range("top_n", 0, 1, 10)  # Raises ValueError
        >>> require_in_range("top_n", 11, 1, 10)  # Raises ValueError
    """
    if value is None:
        return

    if not (min_val <= value <= max_val):
        raise ValueError(f"{name} must be between {min_val} and {max_val}, got {value}")


def convert_with_validation(data: dict[str, Any], target_type: type[T], context: str) -> T:
    """Convert dict to typed model with detailed error logging on validation failure.

    Args:
        data: Dictionary to convert
        target_type: Target msgspec.Struct type
        context: Context description for error message (e.g., "create index request")

    Returns:
        Converted typed model

    Raises:
        ValueError: If validation fails, with full request data in message

    Example:
        >>> from pinecone.models.indexes import CreateIndexRequest
        >>> request = convert_with_validation(
        ...     {"schema": {...}, "name": "my-index"}, CreateIndexRequest, "create index request"
        ... )
    """
    from pinecone._internal.logging import get_logger

    logger = get_logger(__name__)

    try:
        return msgspec.convert(data, target_type)
    except msgspec.ValidationError as e:
        # Log full data for debugging
        logger.exception(
            f"{context.capitalize()} validation failed",
            error=str(e),
            data=json.dumps(data, indent=2, default=str),
        )
        raise ValueError(
            f"Invalid {context}: {e}\n\nData:\n{json.dumps(data, indent=2, default=str)}"
        ) from e


def parse_response_with_validation(
    response: httpx.Response,
    parser: ResponseParser[ResponseT_co],
    context: str,
) -> ResponseT_co:
    """Parse API response with detailed error logging on validation failure.

    Args:
        response: HTTP response object
        parser: Parser object with from_response(json) method
        context: Context description for error message (e.g., "create index response")

    Returns:
        Parsed response object

    Raises:
        ValueError: If validation fails, with full response body in message

    Example:
        >>> from pinecone._internal.adapters.indexes import create_adapter
        >>> index = parse_response_with_validation(
        ...     response, create_adapter, "create index response"
        ... )
    """
    from pinecone._internal.logging import get_logger

    logger = get_logger(__name__)

    try:
        return parser.from_response(response.json())
    except msgspec.ValidationError as e:
        # Log full response for debugging
        logger.exception(
            f"{context.capitalize()} validation failed",
            error=str(e),
            response_body=response.text,
            status_code=response.status_code,
        )
        raise ValueError(
            f"Failed to parse {context}: {e}\n\nResponse body:\n{response.text}"
        ) from e
