"""Main client class for the Dell AI SDK."""

from typing import Dict, List, Any, Optional, TYPE_CHECKING
import json

import requests

from dell_ai import constants, auth
from dell_ai.exceptions import (
    APIError,
    AuthenticationError,
    ResourceNotFoundError,
    ValidationError,
)

if TYPE_CHECKING:
    from dell_ai.models import Model
    from dell_ai.platforms import Platform
    from dell_ai.apps import App


class DellAIClient:
    """Main client for interacting with the Dell Enterprise Hub (DEH) API."""

    def __init__(self, token: Optional[str] = None):
        """
        Initialize the Dell AI client.

        Args:
            token: Hugging Face API token. If not provided, will attempt to load from
                  the Hugging Face token cache.

        Raises:
            AuthenticationError: If a token is provided but invalid
        """
        self.base_url = constants.API_BASE_URL
        self.session = requests.Session()

        # Set default headers
        self.session.headers.update(
            {
                "Content-Type": "application/json",
                "Accept": "application/json",
                "User-Agent": "dell-ai-sdk/python",
            }
        )

        # Set up authentication
        self.token = token or auth.get_token()
        if self.token:
            # If token was explicitly provided, validate it
            if token and not auth.validate_token(token):
                raise AuthenticationError("Invalid authentication token provided.")

            self.session.headers.update({"Authorization": f"Bearer {self.token}"})

    def _make_request(
        self,
        method: str,
        endpoint: str,
        params: Optional[Dict] = None,
        data: Optional[Dict] = None,
    ) -> Dict[str, Any]:
        """
        Make an HTTP request to the API.

        Args:
            method: HTTP method (GET, POST, etc.)
            endpoint: API endpoint
            params: Query parameters
            data: Request body data

        Returns:
            Response data as a dictionary

        Raises:
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
            ResourceNotFoundError: If the requested resource is not found
            ValidationError: If the input parameters are invalid
        """
        url = f"{self.base_url}{endpoint}"

        try:
            response = self.session.request(
                method=method, url=url, params=params, json=data
            )
            response.raise_for_status()

            # Ensure we have a valid JSON response
            try:
                return response.json()
            except json.JSONDecodeError:
                raise APIError(
                    "Invalid JSON response from API",
                    status_code=response.status_code,
                    response=response.text,
                )

        except requests.exceptions.HTTPError as e:
            error_message = f"HTTP Error: {e}"

            try:
                error_data = response.json()
                if "message" in error_data:
                    error_message = error_data["message"]
            except (json.JSONDecodeError, AttributeError):
                # Use the response text if can't parse JSON
                if response.text:
                    error_message = response.text

            if response.status_code == 401:
                raise AuthenticationError(
                    "Authentication failed. Please check your token or login again."
                )
            elif response.status_code == 404:
                # Extract resource type and ID from the endpoint
                parts = endpoint.strip("/").split("/")
                resource_type = parts[0] if parts else "resource"
                resource_id = parts[-1] if len(parts) > 1 else "unknown"
                raise ResourceNotFoundError(resource_type, resource_id)
            elif response.status_code == 400:
                raise ValidationError(f"Invalid request: {error_message}")
            else:
                raise APIError(
                    error_message,
                    status_code=response.status_code,
                    response=response.text,
                )
        except requests.exceptions.ConnectionError as e:
            raise APIError(f"Connection error: {str(e)}")
        except requests.exceptions.Timeout as e:
            raise APIError(f"Request timed out: {str(e)}")
        except requests.exceptions.RequestException as e:
            raise APIError(f"Request failed: {str(e)}")

    def is_authenticated(self) -> bool:
        """
        Check if the client has a valid authentication token.

        Returns:
            True if the token is valid, False otherwise
        """
        if not self.token:
            return False

        try:
            return auth.validate_token(self.token)
        except Exception:
            return False

    def get_user_info(self) -> Dict[str, Any]:
        """
        Get information about the authenticated user.

        Returns:
            A dictionary with user information

        Raises:
            AuthenticationError: If authentication fails or no token is available
        """
        if not self.token:
            raise AuthenticationError(
                "No authentication token available. Please login first."
            )

        return auth.get_user_info(self.token)

    def list_models(self) -> List[str]:
        """
        Get a list of all available model IDs.

        Returns:
            A list of model IDs in the format "organization/model_name"

        Raises:
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import models

        return models.list_models(self)

    def get_model(self, model_id: str) -> "Model":
        """
        Get detailed information about a specific model.

        Args:
            model_id: The model ID in the format "organization/model_name"

        Returns:
            Detailed model information as a Model object

        Raises:
            ValidationError: If the model_id format is invalid
            ResourceNotFoundError: If the model is not found
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import models

        return models.get_model(self, model_id)

    def list_platforms(self) -> List[str]:
        """
        Get a list of all available platform SKU IDs.

        Returns:
            A list of platform SKU IDs

        Raises:
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import platforms

        return platforms.list_platforms(self)

    def get_platform(self, platform_id: str) -> "Platform":
        """
        Get detailed information about a specific platform.

        Args:
            platform_id: The platform SKU ID

        Returns:
            Detailed platform information as a Platform object

        Raises:
            ResourceNotFoundError: If the platform is not found
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import platforms

        return platforms.get_platform(self, platform_id)

    def check_model_access(self, model_id: str) -> bool:
        """
        Check if the authenticated user has access to a specific model repository.

        Args:
            model_id: The model ID in the format "organization/model_name"

        Returns:
            True if the user has access to the model repository

        Raises:
            AuthenticationError: If no token is available or authentication fails
            GatedRepoAccessError: If the repository is gated and the user doesn't have access
            ResourceNotFoundError: If the model doesn't exist
        """
        from dell_ai import auth

        return auth.check_model_access(model_id, self.token)

    def get_deployment_snippet(
        self,
        model_id: str,
        platform_id: str,
        engine: str,
        num_gpus: int,
        num_replicas: int,
    ) -> str:
        """
        Get a deployment snippet for the specified model and configuration.

        Args:
            model_id: The model ID in the format "organization/model_name"
            platform_id: The platform SKU ID
            engine: The deployment engine ("docker" or "kubernetes")
            num_gpus: The number of GPUs to use
            num_replicas: The number of replicas to deploy

        Returns:
            A string containing the deployment snippet (docker command or k8s manifest)

        Raises:
            ValidationError: If any of the input parameters are invalid
            ResourceNotFoundError: If the model or platform is not found
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import models

        return models.get_deployment_snippet(
            self,
            model_id=model_id,
            platform_id=platform_id,
            engine=engine,
            num_gpus=num_gpus,
            num_replicas=num_replicas,
        )

    def list_apps(self) -> List[str]:
        """
        Get a list of all available application names.

        Returns:
            A list of application names

        Raises:
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import apps

        return apps.list_apps(self)

    def get_app(self, app_id: str) -> "App":
        """
        Get detailed information about a specific application.

        Args:
            app_id: The application ID

        Returns:
            Detailed application information as an App object

        Raises:
            ResourceNotFoundError: If the application is not found
            AuthenticationError: If authentication fails
            APIError: If the API returns an error
        """
        from dell_ai import apps

        return apps.get_app(self, app_id)

    def get_app_snippet(self, app_id: str, config: List[Dict[str, Any]]) -> str:
        """
        Get a deployment snippet for the specified app with the provided configuration.

        Args:
            app_id: The application ID
            config: List of configuration parameters with helmPath, type, and value

        Returns:
            A string containing the deployment snippet (Helm command)

        Raises:
            ValidationError: If any of the input parameters are invalid
            ResourceNotFoundError: If the application is not found
            APIError: If the API returns an error
        """
        from dell_ai import apps

        return apps.get_app_snippet(self, app_id, config)
