# Copyright 2025 Softwell S.r.l.
# Licensed under the Apache License, Version 2.0

"""Case-insensitive HTTP headers with multi-value support.

Classes:
    Headers -- immutable, case-insensitive header collection (RFC 7230).

Functions:
    headers_from_scope -- factory from ASGI scope["headers"].

Names are normalized to lowercase internally. Values are decoded from
Latin-1 (ASGI convention). Immutable: no mutation methods.
Uses ``__slots__`` for memory efficiency.
"""

from collections.abc import Mapping
from typing import Any, Iterator

__all__ = ["Headers", "headers_from_scope"]


class Headers:
    """
    Immutable, case-insensitive HTTP headers with multi-value support.

    HTTP header names are case-insensitive per RFC 7230. This class normalizes
    header names to lowercase internally while preserving values. The same
    header can appear multiple times (e.g., Set-Cookie).

    ASGI provides headers as ``list[tuple[bytes, bytes]]`` with Latin-1 encoding.
    This class decodes them to strings automatically.

    This class is read-only. For mutable headers (response building), use
    ``list[tuple[bytes, bytes]]`` directly or a future MutableHeaders class.

    Example:
        >>> raw = [(b"Content-Type", b"application/json"), (b"Accept", b"text/html")]
        >>> headers = Headers(raw)
        >>> headers.get("content-type")  # Case-insensitive
        'application/json'
        >>> headers["ACCEPT"]  # Also case-insensitive
        'text/html'
        >>> "content-type" in headers
        True

        # Multi-value headers
        >>> raw = [(b"Set-Cookie", b"a=1"), (b"Set-Cookie", b"b=2")]
        >>> headers = Headers(raw)
        >>> headers.getlist("set-cookie")
        ['a=1', 'b=2']
    """

    __slots__ = ("_headers",)

    def __init__(self, raw_headers: list[tuple[bytes, bytes]]) -> None:
        """
        Initialize Headers from raw ASGI headers.

        Args:
            raw_headers: List of (name, value) byte tuples from ASGI scope.
                         Both name and value are decoded as Latin-1.
                         Names are normalized to lowercase.
        """
        self._headers: list[tuple[str, str]] = []
        for name, value in raw_headers:
            self._headers.append((name.decode("latin-1").lower(), value.decode("latin-1")))

    def get(self, key: str, default: str | None = None) -> str | None:
        """
        Get the first value for a header (case-insensitive).

        Args:
            key: Header name (case-insensitive).
            default: Value to return if header not found.

        Returns:
            The first value for the header, or default if not found.
        """
        key_lower = key.lower()
        for name, value in self._headers:
            if name == key_lower:
                return value
        return default

    def getlist(self, key: str) -> list[str]:
        """
        Get all values for a header (case-insensitive).

        Useful for headers that can appear multiple times like Set-Cookie.

        Args:
            key: Header name (case-insensitive).

        Returns:
            List of all values for the header, empty list if not found.
        """
        key_lower = key.lower()
        return [value for name, value in self._headers if name == key_lower]

    def keys(self) -> list[str]:
        """
        Return unique header names (lowercase).

        Returns:
            List of unique header names in order of first occurrence.
        """
        seen: set[str] = set()
        result: list[str] = []
        for name, _ in self._headers:
            if name not in seen:
                seen.add(name)
                result.append(name)
        return result

    def values(self) -> list[str]:
        """
        Return all header values.

        Returns:
            List of all values in order, including duplicates.
        """
        return [value for _, value in self._headers]

    def items(self) -> list[tuple[str, str]]:
        """
        Return all (name, value) pairs.

        Returns:
            List of tuples with lowercase names, including duplicates.
        """
        return list(self._headers)

    def __getitem__(self, key: str) -> str:
        """
        Get header value by name, raising KeyError if not found.

        Args:
            key: Header name (case-insensitive).

        Returns:
            The first value for the header.

        Raises:
            KeyError: If header is not present.
        """
        value = self.get(key)
        if value is None:
            raise KeyError(key)
        return value

    def __contains__(self, key: object) -> bool:
        """Check if header exists (case-insensitive)."""
        if not isinstance(key, str):
            return False
        return self.get(key) is not None

    def __iter__(self) -> Iterator[str]:
        """Iterate over unique header names."""
        return iter(self.keys())

    def __len__(self) -> int:
        """Return total number of header entries (including duplicates)."""
        return len(self._headers)

    def __repr__(self) -> str:
        """Return string representation for debugging."""
        return f"Headers({self._headers!r})"


def headers_from_scope(scope: Mapping[str, Any]) -> Headers:
    """
    Create Headers instance from ASGI scope.

    Convenience function to extract and parse headers from an ASGI scope dict.

    Args:
        scope: ASGI scope mapping containing "headers" key.

    Returns:
        Headers instance. Returns empty Headers if "headers" not in scope.

    Example:
        >>> scope = {"type": "http", "headers": [(b"host", b"example.com")]}
        >>> headers = headers_from_scope(scope)
        >>> headers.get("host")
        'example.com'
    """
    return Headers(scope.get("headers", []))
