"""Canonicalization helpers for probe transcripts."""

from __future__ import annotations

import gzip
import hashlib
import json
from typing import Any, Dict, Iterable, Tuple
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit


IGNORED_HEADERS_DEFAULT = {
    "date",
    "server",
    "set-cookie",
    "x-request-id",
    "traceparent",
    "tracestate",
}


def canonical_url(url: str, normalize_localhost_port: bool = True) -> str:
    """Canonicalize a URL for comparison.

    Args:
        url: The URL to canonicalize
        normalize_localhost_port: If True, strip port from localhost/127.0.0.1 URLs
            to handle tests that use random ports for local servers
    """
    parts = urlsplit(url)
    query = urlencode(sorted(parse_qsl(parts.query, keep_blank_values=True)))
    hostname = parts.hostname.lower() if parts.hostname else ""

    # Normalize localhost variants
    if hostname in ("localhost", "127.0.0.1", "0.0.0.0", "::1"):
        hostname = "localhost"
        # Strip port for localhost to handle random port assignments
        if normalize_localhost_port:
            netloc = hostname
        else:
            netloc = f"{hostname}:{parts.port}" if parts.port else hostname
    else:
        netloc = f"{hostname}:{parts.port}" if parts.port else hostname

    return urlunsplit((parts.scheme.lower(), netloc, parts.path, query, parts.fragment))


def body_hash(body: bytes, content_type: str | None) -> str:
    if content_type and "json" in content_type.lower():
        try:
            payload = json.loads(body.decode("utf-8"))
            normalized = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
            return hashlib.sha256(normalized).hexdigest()[:16]
        except (ValueError, UnicodeDecodeError):
            pass
    return hashlib.sha256(body).hexdigest()[:16]


def decode_gzip(body: bytes) -> Tuple[bytes, bool]:
    try:
        return gzip.decompress(body), True
    except OSError:
        return body, False


def normalize_headers(headers: Iterable[Tuple[str, str]]) -> Tuple[Dict[str, str], Dict[str, str], list[str]]:
    raw: Dict[str, str] = {}
    canon: Dict[str, str] = {}
    ignored = []
    for key, value in headers:
        lower = key.lower()
        raw[lower] = value
        if lower in IGNORED_HEADERS_DEFAULT:
            ignored.append(key)
            continue
        canon[lower] = value
    return canon, raw, ignored
