from typing import Optional, Literal, TypeVar

import requests
from openkosmos_core.setting import Setting
from pydantic import BaseModel

from openkosmos_ai.common.model import AIBaseConfig, LogMode
from openkosmos_ai.nodes.base_node import BaseStateNode

log = Setting.logger("api")


class AuthHeader(BaseModel):
    content_type: Optional[str] = "application/json"
    key_name: str
    key_value: str

    def to_headers(self):
        return {
            "Content-Type": self.content_type,
            self.key_name: self.key_value
        }


class ApiConfig(AIBaseConfig):
    base_url: str
    api_key: Optional[str] = None
    api_key_name: str = "Authorization"
    api_key_prefix: str = "Bearer "
    auth_mode: Literal["totp", "api_key"] = "api_key"
    api_prefix: str = ""
    log_mode: LogMode = LogMode.NONE

    def create_auth_header(self, api_key=None) -> AuthHeader:
        return AuthHeader(key_name=self.api_key_name,
                          key_value=f"{self.api_key_prefix}{self.api_key if api_key is None else api_key}")

    def get_url(self, path: str):
        return f"{self.base_url}{self.api_prefix}{path}"


API_CONFIG_T = TypeVar("API_CONFIG_T", bound=ApiConfig)


def to_http_params(params: dict | BaseModel = None):
    if isinstance(params, dict):
        return params
    else:
        return None if params is None else params.model_dump(by_alias=True, exclude_none=True)


class ApiNode(BaseStateNode[API_CONFIG_T]):
    def __init__(self, config: API_CONFIG_T):
        super().__init__(config)

        if config.auth_mode == "totp":
            self.auth_totp()

    def auth_totp(self):
        pass

    def auth_by_totp(self):
        return self.api_key

    def get(self, path: str, params: dict | BaseModel = None, api_key=None, return_json=True):
        url = self.config().get_url(path)
        auth_header = self.config().create_auth_header(api_key=api_key)
        if self.config().log_mode != LogMode.NONE:
            log.debug("get: {}".format(url))
        response = requests.get(url,
                                params=to_http_params(params),
                                headers=auth_header.to_headers())
        if self.config().log_mode == LogMode.ALL:
            log.debug("get response: {}".format(response.content))

        if return_json:
            return response.json()
        else:
            return response

    def delete(self, path: str, params: dict | BaseModel = None, json_data=None, api_key=None):
        url = self.config().get_url(path)
        auth_header = self.config().create_auth_header(api_key=api_key)
        if self.config().log_mode != LogMode.NONE:
            log.debug("delete: {}".format(url))

        if json_data is None:
            return requests.delete(url,
                                   params=to_http_params(params),
                                   headers=auth_header.to_headers()).json()
        else:
            return requests.delete(url,
                                   params=to_http_params(params),
                                   json=json_data,
                                   headers=auth_header.to_headers()).json()

    def post(self, path: str, params: dict | BaseModel = None, form_data=None, json_data=None, files=None,
             api_key=None):
        url = self.config().get_url(path)
        auth_header = self.config().create_auth_header(api_key=api_key)

        if self.config().log_mode != LogMode.NONE:
            log.debug("post: {}".format(url))

        if self.config().log_mode == LogMode.ALL:
            if json_data is not None:
                log.debug("post json: {}".format(json_data))
            if form_data is not None:
                log.debug("post form: {}".format(form_data))

        if form_data is not None:
            auth_header.content_type = "multipart/form-data"
            response_result = requests.post(url, params=to_http_params(params),
                                            headers=auth_header.to_headers(),
                                            data=form_data)
        elif json_data is not None:
            response_result = requests.post(url, params=to_http_params(params),
                                            headers=auth_header.to_headers(),
                                            json=json_data)
        elif files is not None:
            auth_header.content_type = None
            response_result = requests.post(url, params=to_http_params(params),
                                            headers=auth_header.to_headers(),
                                            files=files)
        else:
            response_result = requests.post(url, params=to_http_params(params),
                                            headers=auth_header.to_headers())

        if self.config().log_mode == LogMode.ALL:
            log.debug("post response: {}".format(response_result.content))

        return response_result.json()

    def put(self, path: str, params: dict | BaseModel = None, json_data=None, api_key=None):
        url = self.config().get_url(path)
        auth_header = self.config().create_auth_header(api_key=api_key)
        if self.config().log_mode != LogMode.NONE:
            log.debug("put: {}".format(url))
        if json_data is not None:
            return requests.put(url, params=to_http_params(params), headers=auth_header.to_headers(),
                                json=json_data).json()
        else:
            return requests.put(url, params=to_http_params(params), headers=auth_header.to_headers()).json()
