"""Main client class for the AI Inference API."""

from collections.abc import Sequence
from typing import Optional

import requests

from .config import settings
from .exceptions import APIError, AuthenticationError
from .models import InferenceRequest, Model

ERROR_MESSAGES = {
    "auth_failed": "Authentication failed",
    "no_token": "No token received",
    "list_models": "Failed to list models",
    "create_inference": "Failed to create inference",
    "get_result": "Failed to get inference result",
    "list_inferences": "Failed to list inferences",
}


class Client:
    """Main client for interacting with the AI Inference API."""

    def __init__(self):
        """Initialize the client.

        Args:
            settings: Optional settings to override defaults
        """
        self._settings = settings
        self.base_url = self._settings.BASE_URL
        self._token = None
        self._session = requests.Session()

    def authenticate(self, email: str, password: str) -> str:
        """Authenticate with email and password to get a JWT token.

        Args:
            email: User's email
            password: User's password

        Returns:
            str: JWT token for authenticated requests

        Raises:
            AuthenticationError: If authentication fails
        """
        response = self._session.post(f"{self.base_url}/token", json={"email": email, "password": password})

        if response.status_code != 200:
            raise AuthenticationError(ERROR_MESSAGES["auth_failed"])

        data = response.json()
        token = data.get("session").get("access_token")
        if not token:
            raise AuthenticationError(ERROR_MESSAGES["no_token"])

        self._token = token
        self._session.headers.update({"Authorization": f"Bearer {token}"})
        return str(token)

    def list_models(self) -> Sequence[Model]:
        """Get list of available models.

        Returns:
            Sequence[Model]: List of available models

        Raises:
            APIError: If API request fails
        """
        response = self._session.get(f"{self.base_url}/models")

        if response.status_code != 200:
            raise APIError(response.status_code, ERROR_MESSAGES["list_models"])

        return [Model.from_dict(item) for item in response.json()]

    def create_inference(self, model_id: str, file_path: str) -> InferenceRequest:
        """Submit a new inference request.

        Args:
            model_id: ID of the model to use
            file_path: Path to the CSV file to process (max 5MB)

        Returns:
            InferenceRequest: Created inference request

        Raises:
            APIError: If API request fails
        """
        with open(file_path, "rb") as f:
            files = {"file": ("input.csv", f, "text/csv")}
            response = self._session.post(f"{self.base_url}/inferences/", params={"model_id": model_id}, files=files)

        if response.status_code != 201:
            raise APIError(response.status_code, ERROR_MESSAGES["create_inference"])

        return InferenceRequest.from_dict(response.json())

    def get_inference_result(self, inference_request_id: str) -> Optional[bytes]:
        """Get the result file for an inference request.

        Args:
            inference_request_id: ID of the inference request

        Returns:
            Optional[bytes]: Result file content if ready, None if still processing

        Raises:
            APIError: If API request fails
        """
        response = self._session.get(f"{self.base_url}/inferences/result/{inference_request_id}")

        if response.status_code == 202:
            return None  # Still processing
        elif response.status_code == 200:
            return bytes(response.content)
        else:
            raise APIError(response.status_code, ERROR_MESSAGES["get_result"])

    def list_inferences(self) -> Sequence[InferenceRequest]:
        """Get list of all inference requests.

        Returns:
            Sequence[InferenceRequest]: List of inference requests

        Raises:
            APIError: If API request fails
        """
        response = self._session.get(f"{self.base_url}/inferences/")

        if response.status_code != 200:
            raise APIError(response.status_code, ERROR_MESSAGES["list_inferences"])

        return [InferenceRequest.from_dict(item) for item in response.json()]
