"""REST Transport Layer für Questra Data."""

from __future__ import annotations

from typing import Any

import requests
from loguru import logger


class RESTTransport:
    """
    Verwaltet die REST-Transport-Schicht mit Authentifizierung.

    Verantwortlich für:
    - HTTP-Anfragen an REST-Endpunkte
    - Authentifizierung über Access Token
    - Fehlerbehandlung
    """

    def __init__(self, base_url: str, get_access_token_func: callable):
        """
        Initialisiert den REST Transport.

        Args:
            base_url: Basis-URL des REST-APIs (z.B. https://dev.techstack.s2o.dev/dynamic-objects-v2/)
            get_access_token_func: Funktion, die ein gültiges Access Token zurückgibt
        """
        self._base_url = base_url.rstrip("/")
        self._get_access_token = get_access_token_func
        logger.debug("REST Transport initialized", base_url=self._base_url)

    def _get_headers(
        self, additional_headers: dict[str, str] | None = None
    ) -> dict[str, str]:
        """
        Erstellt HTTP-Headers mit Authentifizierung.

        Args:
            additional_headers: Zusätzliche Headers

        Returns:
            Dictionary mit Headers
        """
        access_token = self._get_access_token()

        # Maskiere Token für Logging
        masked_token = (
            f"{access_token[:4]}...{access_token[-4:]}"
            if len(access_token) > 8
            else "***"
        )
        logger.debug(
            "Access token retrieved for REST request", token_preview=masked_token
        )

        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        }

        if additional_headers:
            headers.update(additional_headers)

        return headers

    def _handle_response(self, response: requests.Response) -> Any:
        """
        Behandelt HTTP-Response und Fehler.

        Args:
            response: HTTP-Response-Objekt

        Returns:
            Parsed JSON oder Binary Content

        Raises:
            Exception: Bei HTTP-Fehlern
        """
        logger.debug(
            "REST response received",
            status_code=response.status_code,
            content_type=response.headers.get("Content-Type"),
        )

        # Erfolgreiche Anfragen
        if 200 <= response.status_code < 300:
            content_type = response.headers.get("Content-Type", "")

            # Binary Content (z.B. Dateien)
            if "application/json" not in content_type and response.content:
                logger.debug("Returning binary content", size=len(response.content))
                return response.content

            # JSON Content
            if response.content:
                try:
                    result = response.json()
                    logger.debug(
                        "Parsed JSON response", result_type=type(result).__name__
                    )
                    return result
                except ValueError as e:
                    logger.warning(f"Failed to parse JSON response: {e}")
                    return response.text

            # Leere Success Response (z.B. 200 OK ohne Body)
            return None

        # Fehlerbehandlung
        logger.error(
            "REST request failed",
            status_code=response.status_code,
            url=response.url,
            response_text=response.text[:500],
        )

        # Versuche Error Payload zu parsen
        try:
            error_data = response.json()
            error_msg = error_data.get("message", response.text)
        except ValueError:
            error_msg = response.text

        raise Exception(
            f"REST request failed with status {response.status_code}: {error_msg}"
        )

    def get(
        self,
        path: str,
        params: dict[str, Any] | None = None,
        timeout: int = 30,
    ) -> Any:
        """
        Führt einen GET-Request aus.

        Args:
            path: Pfad relativ zur Basis-URL (z.B. '/file/123')
            params: Query-Parameter
            timeout: Timeout in Sekunden

        Returns:
            Response-Daten (JSON oder Binary)
        """
        url = f"{self._base_url}{path}"
        headers = self._get_headers()

        logger.info("Executing REST GET request", url=url, params=params)

        try:
            response = requests.get(
                url,
                headers=headers,
                params=params,
                timeout=timeout,
                verify=True,
            )
            result = self._handle_response(response)
            logger.info("REST GET request completed successfully")
            return result
        except requests.RequestException as e:
            logger.error("REST GET request failed", error=str(e), url=url)
            raise

    def post(
        self,
        path: str,
        json: Any | None = None,
        files: dict[str, Any] | None = None,
        timeout: int = 30,
    ) -> Any:
        """
        Führt einen POST-Request aus.

        Args:
            path: Pfad relativ zur Basis-URL
            json: JSON-Daten für den Body
            files: Dateien für Multipart-Upload
            timeout: Timeout in Sekunden

        Returns:
            Response-Daten (JSON oder Binary)
        """
        url = f"{self._base_url}{path}"

        # Wenn Dateien hochgeladen werden, keine Content-Type Header setzen
        # (requests setzt automatisch multipart/form-data)
        if files:
            headers = self._get_headers()
            headers.pop("Content-Type", None)
            logger.info(
                "Executing REST POST request with files", url=url, file_count=len(files)
            )
        else:
            headers = self._get_headers()
            logger.info(
                "Executing REST POST request", url=url, has_json=json is not None
            )

        try:
            response = requests.post(
                url,
                headers=headers,
                json=json,
                files=files,
                timeout=timeout,
                verify=True,
            )
            result = self._handle_response(response)
            logger.info("REST POST request completed successfully")
            return result
        except requests.RequestException as e:
            logger.error("REST POST request failed", error=str(e), url=url)
            raise

    def put(
        self,
        path: str,
        json: Any | None = None,
        timeout: int = 30,
    ) -> Any:
        """
        Führt einen PUT-Request aus.

        Args:
            path: Pfad relativ zur Basis-URL
            json: JSON-Daten für den Body
            timeout: Timeout in Sekunden

        Returns:
            Response-Daten (JSON oder Binary)
        """
        url = f"{self._base_url}{path}"
        headers = self._get_headers()

        logger.info("Executing REST PUT request", url=url, has_json=json is not None)

        try:
            response = requests.put(
                url,
                headers=headers,
                json=json,
                timeout=timeout,
                verify=True,
            )
            result = self._handle_response(response)
            logger.info("REST PUT request completed successfully")
            return result
        except requests.RequestException as e:
            logger.error("REST PUT request failed", error=str(e), url=url)
            raise

    def delete(
        self,
        path: str,
        timeout: int = 30,
    ) -> Any:
        """
        Führt einen DELETE-Request aus.

        Args:
            path: Pfad relativ zur Basis-URL
            timeout: Timeout in Sekunden

        Returns:
            Response-Daten (JSON oder Binary)
        """
        url = f"{self._base_url}{path}"
        headers = self._get_headers()

        logger.info("Executing REST DELETE request", url=url)

        try:
            response = requests.delete(
                url,
                headers=headers,
                timeout=timeout,
                verify=True,
            )
            result = self._handle_response(response)
            logger.info("REST DELETE request completed successfully")
            return result
        except requests.RequestException as e:
            logger.error("REST DELETE request failed", error=str(e), url=url)
            raise
