"""
NetOrca SDK v2 Client.

Main API client providing typed resource access with automatic pagination.
"""

from __future__ import annotations

from typing import Any, Dict, Generator, Optional

from requests import Response

from netorca_sdk.v2.auth import NetorcaAuth
from netorca_sdk.v2.exceptions import (
    NetorcaAPIError,
    NetorcaAuthenticationError,
    NetorcaGatewayError,
    NetorcaNotFoundError,
    NetorcaServerUnavailableError,
)
from netorca_sdk.v2.resources.ai_documents import AIDocumentsResource
from netorca_sdk.v2.resources.ai_processors import AIProcessorsResource
from netorca_sdk.v2.resources.ai_validators import (
    AIValidatorResponsesResource,
    AIValidatorsResource,
)
from netorca_sdk.v2.resources.applications import ApplicationsResource
from netorca_sdk.v2.resources.change_instances import ChangeInstancesResource
from netorca_sdk.v2.resources.charges import ChargesResource
from netorca_sdk.v2.resources.deployed_items import DeployedItemsResource
from netorca_sdk.v2.resources.healthchecks import HealthchecksResource
from netorca_sdk.v2.resources.llm_models import LLMModelsResource
from netorca_sdk.v2.resources.pack_profiles import PackProfilesResource
from netorca_sdk.v2.resources.service_configs import ServiceConfigsResource
from netorca_sdk.v2.resources.service_items import ServiceItemsResource
from netorca_sdk.v2.resources.services import ServicesResource
from netorca_sdk.v2.resources.submissions import SubmissionsResource
from netorca_sdk.v2.resources.webhooks import WebhooksResource


class NetOrcaClient:
    """
    NetOrca API Client v2.

    Provides typed, resource-based access to the NetOrca API with automatic
    pagination, proper error handling, and model objects instead of raw dicts.

    Attributes:
        services: Access to Services resource
        service_items: Access to Service Items resource
        deployed_items: Access to Deployed Items resource
        change_instances: Access to Change Instances resource
        service_configs: Access to Service Configs resource
        charges: Access to Charges resource
        applications: Access to Applications resource
        submissions: Access to Submissions resource
        healthchecks: Access to Healthchecks resource
        webhooks: Access to Webhooks resource

    Examples:
        Basic usage with API key authentication:

        >>> from netorca_sdk.v2 import NetOrcaClient
        >>> client = NetOrcaClient(
        ...     fqdn="https://api-generaldemo.demo.netorca.io/v1",
        ...     api_key="YOUR_API_KEY"
        ... )

        List service items with filtering:

        >>> from netorca_sdk.v2 import ServiceItemRuntimeState
        >>> # List all in-service items
        >>> for item in client.service_items.list():
        ...     print(f"{item.name}: {item.runtime_state}")
        >>> # Filter by service
        >>> items = client.service_items.by_service(service_id=123)
        >>> # Filter with enum
        >>> items = client.service_items.filter(
        ...     runtime_state=ServiceItemRuntimeState.IN_SERVICE
        ... )

        Using different contexts (serviceowner vs consumer):

        >>> # Service Owner context (default)
        >>> owner_client = NetOrcaClient(
        ...     fqdn="https://api.example.com/v1",
        ...     api_key="KEY",
        ...     context="serviceowner"
        ... )
        >>> owner_client.services.list()
        >>> # Consumer context
        >>> consumer_client = NetOrcaClient(
        ...     fqdn="https://api.example.com/v1",
        ...     api_key="KEY",
        ...     context="consumer"
        ... )
        >>> consumer_client.submissions.submit(data)
    """

    def __init__(
        self,
        fqdn: str,
        api_key: str,
        context: str = "serviceowner",
        verify_ssl: bool = True,
    ):
        """Initialize NetOrca API client.

        Args:
            fqdn (str): Base API URL (e.g., "https://api.example.com/v1")
            api_key (str): API key for authentication
            context (str, optional): API context - "serviceowner" or "consumer". Defaults to "serviceowner"
            verify_ssl (bool, optional): Whether to verify SSL certificates. Defaults to True
        """
        self.context = context
        self.fqdn = fqdn

        self.auth = NetorcaAuth(
            fqdn=fqdn,
            api_key=api_key,
            verify_ssl=verify_ssl,
        )

    def _build_url(self, endpoint: str, id: Optional[int] = None, custom_path: Optional[str] = None) -> str:
        """Build full URL for an endpoint.

        Args:
            endpoint (str): Endpoint name (e.g., "service_items")
            id (int, optional): Optional resource ID
            custom_path (str, optional): Optional custom path override (e.g., "services/dependant")

        Returns:
            str: Full URL string
        """
        # Use custom path if provided, otherwise use endpoint
        path_part = custom_path if custom_path else endpoint

        # Build path parts
        path_parts = ["orcabase", self.context, path_part]
        if id is not None:
            path_parts.append(str(id))

        # Join with / and add trailing slash
        path = "/".join(path_parts) + "/"

        return f"{self.fqdn}/{path}"

    def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None,
            custom_path: Optional[str] = None) -> Generator[Dict[str, Any], None, None]:
        """Perform GET request with automatic pagination.

        Yields individual items from paginated API responses.

        Args:
            endpoint (str): Endpoint name
            params (dict, optional): Query parameters (filters)
            custom_path (str, optional): Optional custom path override

        Yields:
            dict: Individual response items
        """
        url = self._build_url(endpoint, custom_path=custom_path)
        next_url: Optional[str] = url

        while next_url:
            # Use params only on first request
            response = self.auth.get(next_url, filters=params if next_url == url else None)
            data = self._handle_response(response, endpoint)

            # Yield individual results
            results = data.get('results', [])
            for item in results:
                yield item

            # Get next page URL
            next_url = data.get('next')

    def get_one(self, endpoint: str, id: int, custom_path: Optional[str] = None) -> Dict[str, Any]:
        """Get a single resource by ID.

        Args:
            endpoint (str): Endpoint name
            id (int): Resource ID
            custom_path (str, optional): Optional custom path override

        Returns:
            dict: Response data dict
        """
        url = self._build_url(endpoint, id=id, custom_path=custom_path)
        response = self.auth.get(url)
        return self._handle_response(response, endpoint)

    def post(self, endpoint: str, data: Dict[str, Any],
             custom_path: Optional[str] = None) -> Dict[str, Any]:
        """Perform POST request to create resource.

        Args:
            endpoint (str): Endpoint name
            data (dict): Request payload
            custom_path (str, optional): Optional custom path override

        Returns:
            dict: Response data dict
        """
        url = self._build_url(endpoint, custom_path=custom_path)
        response = self.auth.post(url, data=data)
        return self._handle_response(response, endpoint)

    def patch(self, endpoint: str, id: int, data: Dict[str, Any],
              custom_path: Optional[str] = None) -> Dict[str, Any]:
        """Perform PATCH request to partially update resource.

        Args:
            endpoint (str): Endpoint name
            id (int): Resource ID
            data (dict): Fields to update
            custom_path (str, optional): Optional custom path override

        Returns:
            dict: Response data dict
        """
        url = self._build_url(endpoint, id=id, custom_path=custom_path)
        response = self.auth.patch(url, data=data)
        return self._handle_response(response, endpoint)

    def put(self, endpoint: str, id: int, data: Dict[str, Any],
            custom_path: Optional[str] = None) -> Dict[str, Any]:
        """Perform PUT request to fully update resource.

        Args:
            endpoint (str): Endpoint name
            id (int): Resource ID
            data (dict): Complete resource data
            custom_path (str, optional): Optional custom path override

        Returns:
            dict: Response data dict
        """
        url = self._build_url(endpoint, id=id, custom_path=custom_path)
        response = self.auth.put(url, data=data)
        return self._handle_response(response, endpoint)

    def delete(self, endpoint: str, id: int, custom_path: Optional[str] = None) -> Dict[str, Any]:
        """Perform DELETE request.

        Args:
            endpoint (str): Endpoint name
            id (int): Resource ID
            custom_path (str, optional): Optional custom path override

        Returns:
            dict: Response data dict (usually empty or status message)
        """
        url = self._build_url(endpoint, id=id, custom_path=custom_path)
        response = self.auth.delete(url)
        return self._handle_response(response, endpoint)

    def _handle_response(self, response: Response, endpoint: str) -> Dict[str, Any]:
        """Handle API response and check status.

        Args:
            response (Response): HTTP response
            endpoint (str): Endpoint name for error messages

        Returns:
            dict: Parsed response data

        Raises:
            NetorcaAPIError: On API errors
        """
        status_code = response.status_code

        if status_code in {200, 201}:
            try:
                return response.json()
            except ValueError:
                return {"response_text": response.text}
        elif status_code == 204:
            return {"status": "deleted"}
        elif status_code == 400:
            raise NetorcaAPIError(f"Bad request for {endpoint}. Reason: {response.text}")
        elif status_code == 403:
            raise NetorcaAPIError(f"Access denied for {endpoint}.")
        elif status_code == 404:
            raise NetorcaNotFoundError(f"{endpoint} not found.")
        elif status_code == 401:
            raise NetorcaAuthenticationError("Authentication failed.")
        elif status_code == 502:
            raise NetorcaGatewayError("Load balancer or webserver is down.")
        elif status_code == 503:
            raise NetorcaServerUnavailableError("Server is temporarily unavailable.")
        else:
            raise NetorcaAPIError(f"Unexpected error {status_code} for {endpoint}.")
        return {}

    # Resource properties

    @property
    def services(self) -> ServicesResource:
        """Access Services resource."""
        return ServicesResource(self)

    @property
    def service_items(self) -> ServiceItemsResource:
        """Access Service Items resource."""
        return ServiceItemsResource(self)

    @property
    def deployed_items(self) -> DeployedItemsResource:
        """Access Deployed Items resource."""
        return DeployedItemsResource(self)

    @property
    def change_instances(self) -> ChangeInstancesResource:
        """Access Change Instances resource."""
        return ChangeInstancesResource(self)

    @property
    def service_configs(self) -> ServiceConfigsResource:
        """Access Service Configs resource."""
        return ServiceConfigsResource(self)

    @property
    def charges(self) -> ChargesResource:
        """Access Charges resource."""
        return ChargesResource(self)

    @property
    def applications(self) -> ApplicationsResource:
        """Access Applications resource."""
        return ApplicationsResource(self)

    @property
    def submissions(self) -> SubmissionsResource:
        """Access Submissions resource."""
        return SubmissionsResource(self)

    @property
    def healthchecks(self) -> HealthchecksResource:
        """Access Healthchecks resource."""
        return HealthchecksResource(self)

    @property
    def webhooks(self) -> WebhooksResource:
        """Access Webhooks resource."""
        return WebhooksResource(self)

    # AI Resources

    @property
    def ai_documents(self) -> AIDocumentsResource:
        """Access AI Documents resource."""
        return AIDocumentsResource(self)

    @property
    def ai_processors(self) -> AIProcessorsResource:
        """Access AI Processors resource."""
        return AIProcessorsResource(self)

    @property
    def ai_validators(self) -> AIValidatorsResource:
        """Access AI Validators resource."""
        return AIValidatorsResource(self)

    @property
    def ai_validator_responses(self) -> AIValidatorResponsesResource:
        """Access AI Validator Responses resource."""
        return AIValidatorResponsesResource(self)

    @property
    def llm_models(self) -> LLMModelsResource:
        """Access LLM Models resource."""
        return LLMModelsResource(self)

    @property
    def pack_profiles(self) -> PackProfilesResource:
        """Access Pack Service Profiles resource."""
        return PackProfilesResource(self)
