"""Local bounded HTTP fetch fallback for desktop agent (Phase E)."""

from __future__ import annotations

import hashlib
import os
import re
from html.parser import HTMLParser
from typing import Any
from urllib.parse import urlparse
from urllib.request import Request, urlopen


def _validate_public_url(url: str) -> str:
    """Local copy of SSRF checks (desktop agent must not import server app.*)."""
    import ipaddress
    import socket

    raw = str(url or "").strip()
    if not raw:
        raise ValueError("missing_url")
    parsed = urlparse(raw)
    if parsed.scheme not in {"http", "https"}:
        raise ValueError("invalid_url_scheme")
    if parsed.username or parsed.password:
        raise ValueError("url_userinfo_denied")
    host = (parsed.hostname or "").strip().lower().rstrip(".")
    if not host:
        raise ValueError("missing_url_host")
    blocked_exact = {
        "localhost",
        "metadata.google.internal",
        "metadata",
        "kubernetes.default",
        "kubernetes.default.svc",
    }
    blocked_suffixes = (".local", ".localhost", ".internal", ".lan", ".intranet")
    if host in blocked_exact or any(host.endswith(s) for s in blocked_suffixes):
        raise ValueError("ssrf_blocked_host")

    def _ip_blocked(ip: ipaddress.IPv4Address | ipaddress.IPv6Address) -> bool:
        return bool(
            ip.is_private
            or ip.is_loopback
            or ip.is_link_local
            or ip.is_reserved
            or ip.is_multicast
            or ip.is_unspecified
        )

    try:
        if _ip_blocked(ipaddress.ip_address(host)):
            raise ValueError("ssrf_blocked_host")
        return raw
    except ValueError as exc:
        if str(exc) == "ssrf_blocked_host":
            raise
    try:
        infos = socket.getaddrinfo(host, None, type=socket.SOCK_STREAM)
    except socket.gaierror as exc:
        raise ValueError("ssrf_blocked_host") from exc
    if not infos:
        raise ValueError("ssrf_blocked_host")
    for info in infos:
        sockaddr = info[4]
        if not sockaddr:
            continue
        try:
            if _ip_blocked(ipaddress.ip_address(sockaddr[0])):
                raise ValueError("ssrf_blocked_host")
        except ValueError as exc:
            if str(exc) == "ssrf_blocked_host":
                raise
            raise ValueError("ssrf_blocked_host") from exc
    return raw


class _HtmlTextExtractor(HTMLParser):
    def __init__(self) -> None:
        super().__init__()
        self._skip = False
        self.text_parts: list[str] = []

    def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
        if tag in {"script", "style", "noscript"}:
            self._skip = True

    def handle_endtag(self, tag: str) -> None:
        if tag in {"script", "style", "noscript"}:
            self._skip = False

    def handle_data(self, data: str) -> None:
        if self._skip:
            return
        stripped = data.strip()
        if stripped:
            self.text_parts.append(stripped)


def _env_int(name: str, default: int) -> int:
    raw = os.getenv(name)
    if raw is None:
        return default
    try:
        return int(raw)
    except ValueError:
        return default


def _env_float(name: str, default: float) -> float:
    raw = os.getenv(name)
    if raw is None:
        return default
    try:
        return float(raw)
    except ValueError:
        return default


def fetch_url_bounded_local(url: str) -> dict[str, Any]:
    try:
        safe_url = _validate_public_url(url)
    except ValueError as exc:
        return {"ok": False, "error": str(exc) or "invalid_url", "url": str(url or "")}

    max_bytes = _env_int("AVATAR_WEB_FETCH_MAX_BYTES", 524288)
    timeout_sec = _env_float("AVATAR_WEB_FETCH_TIMEOUT_SEC", 15.0)
    max_text_chars = _env_int("WEB_FETCH_MAX_CHARS", 12000)

    request = Request(
        safe_url,
        headers={"User-Agent": "IuVe-Desktop-Agent-Fetch/1.0"},
        method="GET",
    )
    try:
        with urlopen(request, timeout=timeout_sec) as response:
            chunks: list[bytes] = []
            total = 0
            while True:
                chunk = response.read(8192)
                if not chunk:
                    break
                total += len(chunk)
                if total > max_bytes:
                    return {"ok": False, "error": "response_too_large", "url": safe_url}
                chunks.append(chunk)
            raw_bytes = b"".join(chunks)
    except Exception as exc:
        return {"ok": False, "error": str(exc) or "fetch_failed", "url": safe_url}

    html = raw_bytes.decode("utf-8", errors="replace")
    parser = _HtmlTextExtractor()
    parser.feed(html)
    text = re.sub(r"\s+", " ", " ".join(parser.text_parts)).strip()
    truncated = len(text) > max_text_chars
    if truncated:
        text = text[:max_text_chars]
    content_hash = hashlib.sha256(text.encode("utf-8")).hexdigest()
    return {
        "ok": True,
        "url": safe_url,
        "text": text,
        "text_length": len(text),
        "content_hash": content_hash,
        "truncated": truncated,
    }
