"""Generated Python client for {{ app_title }}.

This client was automatically generated from the function's introspection endpoint.
It includes all Pydantic models and typed methods for calling the function.

Supports both local devserver and deployed Cognite Functions.
"""

import inspect
import re
from typing import Any, overload

from cognite.client import CogniteClient
from pydantic import BaseModel
from cognite_typed_functions.models import CogniteTypedError, CogniteTypedResponse

{% for import_stmt in imports %}
{{ import_stmt }}  # type: ignore[reportUnusedImport]
{% endfor %}


{% if models %}
# ===== Model Definitions =====

{% for model in models %}
{{ model.source }}

{% endfor %}
{% endif %}

# ===== Client Implementation =====

class {{ class_name }}:
    """Generated client for {{ app_title }}.

    Supports two modes:
    1. Local devserver: Pass base_url (requires httpx)
    2. Deployed function: Pass cognite_client and function_external_id or function_id
    """

    @overload
    def __init__(self, *, base_url: str) -> None: ...

    @overload
    def __init__(self, *, cognite_client: CogniteClient, function_id: int) -> None: ...

    @overload
    def __init__(self, *, cognite_client: CogniteClient, function_external_id: str) -> None: ...

    def __init__(
        self,
        *,
        base_url: str | None = None,
        cognite_client: CogniteClient | None = None,
        function_id: int | None = None,
        function_external_id: str | None = None,
    ) -> None:
        """Initialize the {{ app_title }} client.

        Args:
            base_url: Base URL for local devserver (e.g., 'http://localhost:8000')
            cognite_client: CogniteClient instance for deployed functions
            function_id: ID of deployed function
            function_external_id: External ID of deployed function

        Example:
            # Local devserver
            client = {{ class_name }}(base_url="http://localhost:8000")

            # Deployed function
            from cognite.client import CogniteClient
            cognite = CogniteClient(...)
            client = {{ class_name }}(cognite_client=cognite, function_external_id="my-function")
        """
        if base_url:
            # Devserver mode
            try:
                import httpx  # type: ignore[import-not-found]  # noqa: F401
            except ImportError as e:
                raise ImportError(
                    "Devserver mode requires httpx. Install it with: pip install httpx"
                ) from e
            self.base_url = base_url.rstrip('/')
            self._cognite_client = None
            self._function_id = None
            self._function_external_id = None
            self._function = None
            self._is_deployed = False
        elif cognite_client and (function_external_id or function_id):
            # Deployed function mode
            self._cognite_client = cognite_client
            self._function_id = function_id
            self._function_external_id = function_external_id
            self._function = None  # Retrieved lazily
            self._is_deployed = True
            self.base_url = "__deployed__"  # Placeholder
        else:
            raise ValueError(
                "Either base_url or (cognite_client with function_external_id or function_id) must be provided"
            )

    def _ensure_function_retrieved(self) -> None:
        """Lazily retrieve the deployed function if not already retrieved."""
        if not self._is_deployed or self._function is not None:
            return

        if self._cognite_client is None:
            raise RuntimeError("CogniteClient not available")

        try:
            if self._function_external_id:
                self._function = self._cognite_client.functions.retrieve(external_id=self._function_external_id)
            elif self._function_id:
                self._function = self._cognite_client.functions.retrieve(id=self._function_id)
            else:
                raise RuntimeError("Neither function_id nor function_external_id provided")

            if self._function is None:
                raise RuntimeError("Function not found")
        except Exception as e:
            raise RuntimeError(f"Failed to retrieve function: {e}") from e

    def _call_deployed(
        self,
        path: str,
        method: str,
        body: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None
    ) -> Any:
        """Call deployed function using Cognite SDK.

        Args:
            path: Path to call (e.g., "/items/123")
            method: HTTP method (GET, POST, PUT, DELETE)
            body: Request body for POST/PUT
            params: Query parameters for GET

        Returns:
            Raw response from function call
        """
        self._ensure_function_retrieved()

        if self._function is None:
            raise RuntimeError("Function not retrieved")

        # Build full path with query parameters
        full_path = path
        if params:
            from urllib.parse import urlencode
            query_string = urlencode({k: v for k, v in params.items() if v is not None})
            full_path = f"{path}?{query_string}"

        # Call using SDK
        request_data: dict[str, Any] = {
            "path": full_path,
            "method": method,
        }
        if body:
            request_data["body"] = body

        call = self._function.call(data=request_data, wait=True)
        return call.get_response()

    def _call_devserver(
        self,
        path: str,
        method: str,
        body: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None
    ) -> Any:
        """Call local devserver using httpx.

        Args:
            path: Path to call (e.g., "/items/123")
            method: HTTP method (GET, POST, PUT, DELETE)
            body: Request body for POST/PUT
            params: Query parameters for GET

        Returns:
            Raw response from HTTP call
        """
        import httpx  # type: ignore[import-not-found]

        url = f"{self.base_url}{path}"

        if method == "GET":
            response = httpx.get(url, params=params)
        elif method == "POST":
            response = httpx.post(url, json=body)
        elif method == "PUT":
            response = httpx.put(url, json=body)
        elif method == "DELETE":
            response = httpx.delete(url)
        else:
            raise ValueError(f"Unsupported HTTP method: {method}")

        response.raise_for_status()
        return response.json()

    def _call_method(
        self,
        path: str,
        method: str,
        body: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None
    ) -> Any:
        """Dispatch method call to appropriate backend and unwrap response.

        Args:
            path: Path to call (e.g., "/items/123")
            method: HTTP method (GET, POST, PUT, DELETE)
            body: Request body for POST/PUT
            params: Query parameters for GET

        Returns:
            Unwrapped response data
        """
        # Dispatch to appropriate backend
        if self._is_deployed:
            result = self._call_deployed(path, method, body, params)
        else:
            result = self._call_devserver(path, method, body, params)

        # Unwrap Cognite Functions response format
        typed_response = CogniteTypedResponse.model_validate(result)
        if not typed_response.success:
            error = CogniteTypedError.model_validate(result)
            raise RuntimeError(f"{error.error_type}: {error.message}")

        return typed_response.data  # type: ignore[reportReturnType]

{% for method in methods %}
    def {{ method.name }}(
        self
{%- for param in method.parameters -%}
, {{ param.name }}: {{ param.type }}{% if param.default is defined %} = {{ param.default }}{% endif %}
{%- endfor -%}
) -> {{ method.return_type }}:
        """{{ method.description | indent(8, first=False) }}"""
        # Build URL with path parameters
        path = "{{ method.path }}"
{% for p_name in method.path_params %}
        path = path.replace("{{'{' + p_name + '}'}}", str({{ p_name }}))
{% endfor %}
{% if method.http_method in ['POST', 'PUT'] %}

        # Build request body
        body: dict[str, Any] = {}
{% for param in method.parameters %}
{% if param.type.startswith('list[') %}
        # Serialize list parameter {{ param.name }}
        body['{{ param.name }}'] = [
            item.model_dump() if hasattr(item, 'model_dump') else item
            for item in {{ param.name }}
        ]
{% else %}
        # Serialize {{ param.name }}
        body['{{ param.name }}'] = (
            {{ param.name }}.model_dump() if hasattr({{ param.name }}, 'model_dump')
            else {{ param.name }}
        )
{% endif %}
{% endfor %}

        data = self._call_method(path, "{{ method.http_method }}", body=body)
{% else %}
{% set path_params = method.path_params %}
{% set query_params = [] %}
{% for param in method.parameters %}
{% if param.name not in path_params %}
{% set _ = query_params.append(param) %}
{% endif %}
{% endfor %}
{% if query_params %}

        # Build query parameters
        params: dict[str, Any] = {}
{% for param in query_params %}
{% if param.required %}
        params['{{ param.name }}'] = {{ param.name }}
{% elif param.default == "None" %}
        if {{ param.name }} is not None:
            params['{{ param.name }}'] = {{ param.name }}
{% else %}
        params['{{ param.name }}'] = {{ param.name }}
{% endif %}
{% endfor %}

        data = self._call_method(path, "{{ method.http_method }}", params=params)
{% else %}

        data = self._call_method(path, "{{ method.http_method }}")
{% endif %}
{% endif %}

        # Deserialize response into Pydantic model if return type is a model
        return_type = "{{ method.return_type }}"
        if return_type.startswith("list["):
            # Handle list responses - extract model name
            if match := re.search(r"list\[([A-Z][a-zA-Z0-9_]*)\]", return_type):
                model_name = match.group(1)
                if model_name in globals():
                    model_class = globals()[model_name]
                    # Verify it's actually a Pydantic model class
                    if inspect.isclass(model_class) and issubclass(model_class, BaseModel):
                        if isinstance(data, list):
                            return [model_class.model_validate(item) for item in data]  # type: ignore[reportReturnType]
        elif return_type and return_type[0].isupper():
            # Single model response - check if it's a known model
            # Extract base type name (e.g., "Item" from "Item")
            if match := re.search(r"^([A-Z][a-zA-Z0-9_]*)", return_type):
                model_name = match.group(1)
                if model_name in globals():
                    model_class = globals()[model_name]
                    # Verify it's actually a Pydantic model class
                    if inspect.isclass(model_class) and issubclass(model_class, BaseModel):
                        return model_class.model_validate(data)  # type: ignore[reportReturnType]

        return data

{% endfor %}

