from posixpath import join
from typing import Any, Callable, Dict, List, Optional, Union

from pydantic import UUID4, BaseModel, HttpUrl, SecretStr
from requests import get, post, put

from promptquality.constants.routes import Routes
from promptquality.types.run import (
    CreateIntegrationRequest,
    CreateJobRequest,
    CreateProjectRequest,
    CreateRunRequest,
    CreateTemplateRequest,
    GetMetricsRequest,
    UploadDatasetRequest,
)
from promptquality.utils.request import HttpHeaders, make_request


class ApiClient(BaseModel):
    api_url: HttpUrl
    token: SecretStr

    @staticmethod
    def healthcheck(base_url: str) -> bool:
        make_request(get, base_url=base_url, endpoint=Routes.healthcheck)
        return True

    @staticmethod
    def username_login(base_url: str, username: str, password: str) -> Dict[str, str]:
        return make_request(
            post,
            base_url=base_url,
            endpoint=Routes.login,
            data={
                "username": username,
                "password": password,
                "auth_method": "email",
            },
        )

    @property
    def base_url(self) -> str:
        return self.api_url.unicode_string()

    @property
    def auth_header(self) -> Dict[str, str]:
        return {"Authorization": f"Bearer {self.token.get_secret_value()}"}

    def _make_request(
        self,
        request_method: Callable,
        endpoint: str,
        body: Optional[Dict] = None,
        data: Optional[Dict] = None,
        files: Optional[Dict] = None,
        params: Optional[Dict] = None,
        timeout: Union[int, None] = None,
        json_request_only: bool = False,
    ) -> Any:
        if json_request_only:
            content_headers = HttpHeaders.accept_json()
        else:
            content_headers = HttpHeaders.json()
        headers = {**self.auth_header, **content_headers}
        return make_request(
            request_method=request_method,
            base_url=self.base_url,
            endpoint=endpoint,
            body=body,
            data=data,
            files=files,
            params=params,
            headers=headers,
            timeout=timeout,
        )

    def get_current_user(self) -> Dict[str, str]:
        return self._make_request(get, endpoint=Routes.current_user)

    def create_project(self, project_request: CreateProjectRequest) -> Dict[str, str]:
        return self._make_request(
            post,
            endpoint=Routes.projects,
            body=project_request.model_dump(),
        )

    def create_template(
        self, template_request: CreateTemplateRequest
    ) -> Dict[str, str]:
        return self._make_request(
            post,
            endpoint=Routes.templates.format(project_id=template_request.project_id),
            body=template_request.model_dump(mode="json"),
        )

    def upload_dataset(self, dataset_request: UploadDatasetRequest) -> Dict[str, str]:
        return self._make_request(
            post,
            json_request_only=True,
            endpoint=Routes.dataset.format(project_id=dataset_request.project_id),
            data=dataset_request.data,
            files=dataset_request.files,
        )

    def create_run(self, run_request: CreateRunRequest) -> Dict[str, str]:
        return self._make_request(
            post,
            endpoint=Routes.runs.format(project_id=run_request.project_id),
            body=run_request.model_dump(mode="json"),
        )

    def create_job(self, job_request: CreateJobRequest) -> Dict[str, str]:
        return self._make_request(
            post,
            endpoint=Routes.jobs,
            body=job_request.model_dump(mode="json", exclude_none=True),
        )

    def get_metrics(self, metrics_request: GetMetricsRequest) -> List[Dict[str, Any]]:
        return self._make_request(
            get,
            endpoint=Routes.metrics.format(**metrics_request.model_dump(mode="json")),
        )

    def get_job_status(self, job_id: UUID4) -> Dict[str, Any]:
        return self._make_request(get, endpoint=join(Routes.jobs, str(job_id)))

    def put_integration(
        self, integration_request: CreateIntegrationRequest
    ) -> Dict[str, Any]:
        return self._make_request(
            put,
            endpoint=Routes.integrations.format(
                integration_name=integration_request.name
            ),
            body=integration_request.body,
        )
