"""Models resource for CMDOP LLM service."""

from __future__ import annotations

from typing import TYPE_CHECKING, Optional, Union

import httpx
from openai._compat import cached_property
from openai._resource import AsyncAPIResource, SyncAPIResource
from openai._types import NOT_GIVEN, Body, Headers, NotGiven, Query

from ..types.models import Model, ModelsResponse

if TYPE_CHECKING:
    from .._client import AsyncCmdopLLM, CmdopLLM

__all__ = ["Models", "AsyncModels"]


def _filter_models(
    models: list[Model],
    *,
    provider: Optional[str] = None,
    supports_vision: Optional[bool] = None,
    min_context_length: Optional[int] = None,
    max_prompt_price: Optional[float] = None,
    max_completion_price: Optional[float] = None,
    search: Optional[str] = None,
) -> list[Model]:
    """Apply filters to models list."""
    result = models

    if provider is not None:
        provider_lower = provider.lower()
        result = [m for m in result if m.owned_by.lower() == provider_lower]

    if supports_vision is not None:
        result = [m for m in result if m.supports_vision == supports_vision]

    if min_context_length is not None:
        result = [m for m in result if m.context_length >= min_context_length]

    if max_prompt_price is not None:
        result = [
            m for m in result
            if m.pricing.prompt_cost_per_million() <= max_prompt_price
        ]

    if max_completion_price is not None:
        result = [
            m for m in result
            if m.pricing.completion_cost_per_million() <= max_completion_price
        ]

    if search is not None:
        search_lower = search.lower()
        result = [
            m for m in result
            if search_lower in m.id.lower() or search_lower in m.name.lower()
        ]

    return result


class Models(SyncAPIResource):
    """Models resource.

    Provides access to OpenRouter models list with filtering.

    Example:
        ```python
        from cmdop_llm import CmdopLLM

        client = CmdopLLM()

        # List all models
        models = client.models.list()
        for model in models.data:
            print(f"{model.id}: {model.name}")

        # Filter by provider
        anthropic_models = client.models.list(provider="anthropic")

        # Filter by vision support
        vision_models = client.models.list(supports_vision=True)

        # Filter by context length
        long_context = client.models.list(min_context_length=100000)

        # Filter by price (max $1 per 1M prompt tokens)
        cheap_models = client.models.list(max_prompt_price=1.0)

        # Search by name or ID
        gpt_models = client.models.list(search="gpt-4")

        # Get single model
        model = client.models.retrieve("anthropic/claude-3.5-sonnet")
        print(f"Context: {model.context_length}")
        ```
    """

    _client: CmdopLLM

    @cached_property
    def with_raw_response(self) -> ModelsWithRawResponse:
        """Access raw HTTP response data."""
        return ModelsWithRawResponse(self)

    def list(
        self,
        *,
        provider: Optional[str] = None,
        supports_vision: Optional[bool] = None,
        min_context_length: Optional[int] = None,
        max_prompt_price: Optional[float] = None,
        max_completion_price: Optional[float] = None,
        search: Optional[str] = None,
        refresh: bool = False,
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
    ) -> ModelsResponse:
        """List available models with optional filtering.

        Args:
            provider: Filter by provider name (e.g., "openai", "anthropic").
            supports_vision: Filter for vision-capable models.
            min_context_length: Minimum context length in tokens.
            max_prompt_price: Maximum prompt price per 1M tokens.
            max_completion_price: Maximum completion price per 1M tokens.
            search: Search string to match in model ID or name.
            refresh: Force refresh from API (bypass cache).
            extra_headers: Additional headers to send.
            extra_query: Additional query parameters.
            extra_body: Additional body parameters.
            timeout: Request timeout.

        Returns:
            ModelsResponse with list of models.

        Raises:
            openai.APIError: If the request fails.
        """
        query_params = extra_query or {}
        if refresh:
            query_params = {**query_params, "refresh": "true"}

        response = self._get(
            "/models",
            options={
                "extra_headers": extra_headers,
                "extra_query": query_params if query_params else None,
                "extra_body": extra_body,
                "timeout": timeout,
            },
            cast_to=ModelsResponse,
        )

        # Apply client-side filters
        if any([
            provider is not None,
            supports_vision is not None,
            min_context_length is not None,
            max_prompt_price is not None,
            max_completion_price is not None,
            search is not None,
        ]):
            filtered = _filter_models(
                response.data,
                provider=provider,
                supports_vision=supports_vision,
                min_context_length=min_context_length,
                max_prompt_price=max_prompt_price,
                max_completion_price=max_completion_price,
                search=search,
            )
            return ModelsResponse(data=filtered)

        return response

    def retrieve(
        self,
        model_id: str,
        *,
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
    ) -> Model:
        """Retrieve a specific model by ID.

        Args:
            model_id: The model ID (e.g., "anthropic/claude-3.5-sonnet").
            extra_headers: Additional headers to send.
            extra_query: Additional query parameters.
            extra_body: Additional body parameters.
            timeout: Request timeout.

        Returns:
            Model information.

        Raises:
            openai.NotFoundError: If the model is not found.
            openai.APIError: If the request fails.
        """
        return self._get(
            f"/models/{model_id}",
            options={
                "extra_headers": extra_headers,
                "extra_query": extra_query,
                "extra_body": extra_body,
                "timeout": timeout,
            },
            cast_to=Model,
        )


class AsyncModels(AsyncAPIResource):
    """Async models resource."""

    _client: AsyncCmdopLLM

    @cached_property
    def with_raw_response(self) -> AsyncModelsWithRawResponse:
        """Access raw HTTP response data."""
        return AsyncModelsWithRawResponse(self)

    async def list(
        self,
        *,
        provider: Optional[str] = None,
        supports_vision: Optional[bool] = None,
        min_context_length: Optional[int] = None,
        max_prompt_price: Optional[float] = None,
        max_completion_price: Optional[float] = None,
        search: Optional[str] = None,
        refresh: bool = False,
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
    ) -> ModelsResponse:
        """List available models with optional filtering (async).

        See Models.list for full documentation.
        """
        query_params = extra_query or {}
        if refresh:
            query_params = {**query_params, "refresh": "true"}

        response = await self._get(
            "/models",
            options={
                "extra_headers": extra_headers,
                "extra_query": query_params if query_params else None,
                "extra_body": extra_body,
                "timeout": timeout,
            },
            cast_to=ModelsResponse,
        )

        # Apply client-side filters
        if any([
            provider is not None,
            supports_vision is not None,
            min_context_length is not None,
            max_prompt_price is not None,
            max_completion_price is not None,
            search is not None,
        ]):
            filtered = _filter_models(
                response.data,
                provider=provider,
                supports_vision=supports_vision,
                min_context_length=min_context_length,
                max_prompt_price=max_prompt_price,
                max_completion_price=max_completion_price,
                search=search,
            )
            return ModelsResponse(data=filtered)

        return response

    async def retrieve(
        self,
        model_id: str,
        *,
        extra_headers: Headers | None = None,
        extra_query: Query | None = None,
        extra_body: Body | None = None,
        timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
    ) -> Model:
        """Retrieve a specific model by ID (async).

        See Models.retrieve for full documentation.
        """
        return await self._get(
            f"/models/{model_id}",
            options={
                "extra_headers": extra_headers,
                "extra_query": extra_query,
                "extra_body": extra_body,
                "timeout": timeout,
            },
            cast_to=Model,
        )


class ModelsWithRawResponse:
    """Raw response wrapper for Models resource."""

    def __init__(self, models: Models) -> None:
        self._models = models

    def list(self, **kwargs) -> httpx.Response:  # type: ignore
        """Get raw response from models list."""
        return self._models._client.with_raw_response._get(  # type: ignore
            "/models",
        )

    def retrieve(self, model_id: str, **kwargs) -> httpx.Response:  # type: ignore
        """Get raw response from model retrieve."""
        return self._models._client.with_raw_response._get(  # type: ignore
            f"/models/{model_id}",
        )


class AsyncModelsWithRawResponse:
    """Raw response wrapper for AsyncModels resource."""

    def __init__(self, models: AsyncModels) -> None:
        self._models = models

    async def list(self, **kwargs) -> httpx.Response:  # type: ignore
        """Get raw response from models list."""
        return await self._models._client.with_raw_response._get(  # type: ignore
            "/models",
        )

    async def retrieve(self, model_id: str, **kwargs) -> httpx.Response:  # type: ignore
        """Get raw response from model retrieve."""
        return await self._models._client.with_raw_response._get(  # type: ignore
            f"/models/{model_id}",
        )
