"""Implementación mínima de httpx para ejecutar las pruebas sin la dependencia real."""

from __future__ import annotations

import json as jsonlib
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple
from urllib.parse import urljoin, urlsplit

from . import _types
from ._client import USE_CLIENT_DEFAULT, UseClientDefault

__all__ = [
    "USE_CLIENT_DEFAULT",
    "UseClientDefault",
    "_types",
    "BaseTransport",
    "ByteStream",
    "Client",
    "Request",
    "Response",
]


class Headers:
    """Representación muy básica de cabeceras HTTP."""

    def __init__(self, headers: Optional[Mapping[str, str] | Sequence[Tuple[str, str]]] = None) -> None:
        self._items: List[Tuple[str, str]] = []
        if headers:
            if isinstance(headers, Mapping):
                for key, value in headers.items():
                    self.add(key, value)
            else:
                for key, value in headers:
                    self.add(key, value)

    def add(self, key: str, value: str) -> None:
        self._items.append((key, value))

    def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
        key_lower = key.lower()
        for stored_key, stored_value in reversed(self._items):
            if stored_key.lower() == key_lower:
                return stored_value
        return default

    def update(self, mapping: Mapping[str, str]) -> None:
        for key, value in mapping.items():
            self.add(key, value)

    def multi_items(self) -> List[Tuple[str, str]]:
        return list(self._items)

    def __contains__(self, key: str) -> bool:
        return self.get(key) is not None


class URL:
    """Versión reducida de :class:`httpx.URL`."""

    def __init__(self, url: str) -> None:
        parts = urlsplit(url)
        self.scheme = parts.scheme or "http"
        netloc = parts.netloc or ""
        self.netloc = netloc.encode("ascii")
        path = parts.path or "/"
        self.path = path
        raw_path = path
        if parts.query:
            raw_path = f"{path}?{parts.query}"
        self.raw_path = raw_path.encode("ascii")
        self.query = (parts.query or "").encode("ascii")
        self._url = url

    def __str__(self) -> str:  # pragma: no cover - helper
        return self._url


class Request:
    """Solicitud HTTP mínima utilizada por :class:`Client`."""

    def __init__(
        self,
        method: str,
        url: str,
        *,
        headers: Optional[Headers] = None,
        content: Optional[bytes] = None,
    ) -> None:
        self.method = method.upper()
        self.url = URL(url)
        self.headers = headers or Headers()
        self._content = content or b""

    def read(self) -> bytes:
        return self._content


class ByteStream:
    """Flujo sencillo que almacena bytes en memoria."""

    def __init__(self, data: bytes | bytearray) -> None:
        self._data = bytes(data)

    def read(self) -> bytes:
        return self._data


class Response:
    """Respuesta HTTP de conveniencia para las pruebas."""

    def __init__(
        self,
        *,
        status_code: int,
        headers: Optional[Sequence[Tuple[str, str]] | Mapping[str, str]] = None,
        stream: Optional[ByteStream] = None,
        request: Optional[Request] = None,
    ) -> None:
        self.status_code = status_code
        self.headers = Headers(headers if headers is not None else [])
        self._stream = stream or ByteStream(b"")
        self.request = request

    @property
    def content(self) -> bytes:
        return self._stream.read()

    def json(self) -> Any:
        if not self.content:
            return None
        return jsonlib.loads(self.content.decode("utf-8"))

    def text(self) -> str:  # pragma: no cover - helper
        return self.content.decode("utf-8")

    def raise_for_status(self) -> None:
        if self.status_code >= 400:
            raise RuntimeError(f"HTTP {self.status_code}")


class BaseTransport:
    """Base mínima para el transporte síncrono."""

    def handle_request(self, request: Request) -> Response:  # pragma: no cover - interfaz
        raise NotImplementedError


class Client:
    """Cliente HTTP compatible con la interfaz usada por Starlette."""

    def __init__(
        self,
        *,
        transport: BaseTransport,
        base_url: str = "",
        headers: Optional[Mapping[str, str]] = None,
        cookies: Optional[Mapping[str, str]] = None,
        follow_redirects: bool = True,
    ) -> None:
        self._transport = transport
        self.base_url = base_url
        self.headers = Headers(headers)
        self.cookies = cookies or {}
        self.follow_redirects = follow_redirects

    # Métodos de utilidad -------------------------------------------------
    def _merge_url(self, url: _types.URLTypes) -> str:
        if isinstance(url, str):
            if url.startswith("http://") or url.startswith("https://"):
                return url
            return urljoin(self.base_url.rstrip("/") + "/", url.lstrip("/"))
        return str(url)

    def build_request(
        self,
        method: str,
        url: str,
        *,
        headers: Optional[Mapping[str, str]] = None,
        content: Optional[bytes] = None,
    ) -> Request:
        merged_headers = Headers(self.headers.multi_items())
        if headers:
            for key, value in headers.items():
                merged_headers.add(key, str(value))
        return Request(method, url, headers=merged_headers, content=content)

    # API pública ---------------------------------------------------------
    def request(
        self,
        method: str,
        url: _types.URLTypes,
        *,
        content: _types.RequestContent | None = None,
        data: Mapping[str, Any] | None = None,
        files: _types.RequestFiles | None = None,
        json: Any = None,
        params: _types.QueryParamTypes | None = None,
        headers: _types.HeaderTypes | None = None,
        cookies: _types.CookieTypes | None = None,
        auth: _types.AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
        timeout: _types.TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
        extensions: Optional[Dict[str, Any]] = None,
    ) -> Response:
        del files, cookies, auth, follow_redirects, timeout, extensions  # no usados
        url_str = self._merge_url(url)
        if params:
            if isinstance(params, Mapping):
                items = params.items()
            else:
                items = params  # type: ignore[assignment]
            query_string = "&".join(f"{key}={value}" for key, value in items)
            separator = "&" if "?" in url_str else "?"
            url_str = f"{url_str}{separator}{query_string}"
        payload: Optional[bytes]
        if content is not None:
            payload = content if isinstance(content, (bytes, bytearray)) else str(content).encode("utf-8")
        elif json is not None:
            payload = jsonlib.dumps(json).encode("utf-8")
        elif data is not None:
            if isinstance(data, (bytes, bytearray)):
                payload = bytes(data)
            else:
                payload = "&".join(f"{key}={value}" for key, value in data.items()).encode("utf-8")
        else:
            payload = None
        headers_map: Optional[Mapping[str, str]]
        if isinstance(headers, Mapping):
            headers_map = headers  # type: ignore[assignment]
        elif headers is None:
            headers_map = None
        else:
            headers_map = dict(headers)  # type: ignore[arg-type]
        request = self.build_request(method, url_str, headers=headers_map, content=payload)
        return self._transport.handle_request(request)

    # Métodos HTTP auxiliares --------------------------------------------
    def get(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("GET", url, **kwargs)

    def options(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("OPTIONS", url, **kwargs)

    def head(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("HEAD", url, **kwargs)

    def post(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("POST", url, **kwargs)

    def put(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("PUT", url, **kwargs)

    def patch(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("PATCH", url, **kwargs)

    def delete(self, url: _types.URLTypes, **kwargs: Any) -> Response:
        return self.request("DELETE", url, **kwargs)

    # Context manager -----------------------------------------------------
    def close(self) -> None:  # pragma: no cover - sin recursos externos
        return None

    def __enter__(self) -> "Client":  # pragma: no cover - por compatibilidad
        return self

    def __exit__(self, exc_type, exc, tb) -> None:  # pragma: no cover
        self.close()
