from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
from typing import Any, Dict, Optional
import httpx
from json import JSONDecodeError

from app.components.http.infrastructure.repositories.responser import APIResponser
from app.components.http.domain.repositories.http_repository import HttpRepository

class HttpRepositoryImpl(HttpRepository):
    def __init__(self, base_url: str, headers: Optional[Dict[str, str]] = None):
        self.base_url = base_url
        # Asegura que pedimos JSON siempre
        self.headers = {"Accept": "application/json", **(headers or {})}

    def _build_url(self, path: str) -> str:
        return f"{self.base_url}{path}"

    @retry(
        stop=stop_after_attempt(3),
        wait=wait_fixed(2),
        retry=retry_if_exception_type(httpx.RequestError),
        reraise=True
    )
    async def _request(
        self,
        method: str,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        config: Optional[Dict[str, Any]] = None
    ) -> Any:
        url = self._build_url(path)

        # Merge headers y elimina None
        request_headers = {**self.headers, **(config.get("headers") if config else {})}
        headers = {k: v for k, v in request_headers.items() if v is not None}

        timeout = (config or {}).get("timeout", 5)

        # Si usas GET con body JSON, httpx lo permite, pero pon Content-Type
        request_kwargs: Dict[str, Any] = {
            "method": method,
            "url": url,
            "headers": headers,
            "timeout": timeout,
        }

        if method.upper() in ("GET", "DELETE"):
            # Tu caso: GET con body JSON
            if params is not None:
                request_kwargs["json"] = params
                if "Content-Type" not in request_kwargs["headers"]:
                    request_kwargs["headers"]["Content-Type"] = "application/json"
        else:
            request_kwargs["json"] = params or {}
            if "Content-Type" not in request_kwargs["headers"]:
                request_kwargs["headers"]["Content-Type"] = "application/json"

        try:
            async with httpx.AsyncClient(follow_redirects=True) as client:
                response = await client.request(**request_kwargs)

            response.raise_for_status()

            # 204 o body vacío -> no intentes parsear JSON
            if response.status_code == 204 or not response.content:
                return APIResponser.success(data=None, code=response.status_code)

            return APIResponser.success(data=response.json(), code=response.status_code)

        except httpx.HTTPStatusError as error:
            resp = error.response
            status = resp.status_code if resp is not None else 500
            content_type = resp.headers.get("content-type", "") if resp is not None else ""
            raw_text = resp.text if (resp is not None and resp.text) else ""

            payload: Dict[str, Any] = {}
            if resp is not None and resp.content:
                try:
                    payload = resp.json()
                except (JSONDecodeError, ValueError):
                    payload = {"detail": raw_text or "No response body", "content_type": content_type}

            message = (
                (payload.get("errors") if isinstance(payload, dict) else None)
                or (payload.get("detail") if isinstance(payload, dict) else None)
                or f"HTTP {status} - {content_type or 'no content-type'}"
            )

            return APIResponser.error_message(message=message, code=status)

        except httpx.RequestError as error:
            return APIResponser.error_response(
                message=f"External service connection failed with error: {str(error)}",
                code=503
            )

    async def get(self, path: str, params: Optional[Dict[str, Any]] = None, config: Optional[Dict[str, Any]] = None) -> Any:
        return await self._request("GET", path, params, config)

    async def post(self, path: str, params: Optional[Dict[str, Any]] = None, config: Optional[Dict[str, Any]] = None) -> Any:
        return await self._request("POST", path, params, config)

    async def put(self, path: str, params: Optional[Dict[str, Any]] = None, config: Optional[Dict[str, Any]] = None) -> Any:
        return await self._request("PUT", path, params, config)

    async def delete(self, path: str, params: Optional[Dict[str, Any]] = None, config: Optional[Dict[str, Any]] = None) -> Any:
        return await self._request("DELETE", path, params, config)