from typing import Dict, Optional, final

import requests
import xmltodict
from rest_framework import status


class BaseAPI:
    __api_name__ = "BaseAPI"

    def __init__(self):
        pass

    def get_host(self) -> str:
        raise NotImplementedError("Subclasses must implement _get_host() method")

    def build_headers(self) -> Dict[str, str]:
        return {}

    def build_params(self) -> Dict[str, str]:
        return {}

    def repair_authentication(self) -> bool:
        """Callback to repair authentication, e.g. refresh token or re-login."""
        return False

    @final
    def request(
        self,
        method: str,
        path: str,
        data: Optional[Dict | str | bytes] = None,
        json: Optional[Dict] = None,
        headers: Optional[Dict[str, str]] = None,
        params: Optional[Dict[str, str]] = None,
        custom_host: Optional[str] = None,
        timeout: int = 30,
        use_build_headers: bool = True,
    ) -> Dict:
        host = custom_host or self.get_host()
        url = f"{host}{path}"
        combined_headers: Optional[Dict[str, str]] = {
            **(headers or {}),
        }
        if use_build_headers:
            combined_headers = {
                **self.build_headers(),
                **(headers or {}),
            }
        combined_params: Optional[Dict[str, str]] = {
            **self.build_params(),
            **(params or {}),
        }

        if combined_headers == {}:
            combined_headers = None
        if combined_params == {}:
            combined_params = None

        try:
            response = requests.request(
                method=method,
                url=url,
                headers=combined_headers,
                params=combined_params,
                data=data,
                json=json,
                timeout=timeout,
            )
            if (
                response.status_code == status.HTTP_401_UNAUTHORIZED
                or response.status_code == status.HTTP_403_FORBIDDEN
            ):
                if self.repair_authentication():
                    host = custom_host or self.get_host()
                    url = f"{host}{path}"
                    combined_headers: Optional[Dict[str, str]] = {
                        **self.build_headers(),
                        **(headers or {}),
                    }
                    combined_params: Optional[Dict[str, str]] = {
                        **self.build_params(),
                        **(params or {}),
                    }
                    response = requests.request(
                        method=method,
                        url=url,
                        headers=combined_headers,
                        params=combined_params,
                        data=data,
                        timeout=timeout,
                    )
            response.raise_for_status()
            if response.status_code == status.HTTP_204_NO_CONTENT:
                return {}

            content_type = response.headers.get("Content-Type", "")

            if "xml" in content_type:
                return xmltodict.parse(response.text)
            if "application/json" in content_type:
                return response.json()
            else:
                raise ValueError(
                    "Unsupported Content-Type: {content_type} must be application/json"
                )

        except requests.exceptions.RequestException as e:
            raise e
        except ValueError as e:
            raise e
 