import ipaddress
import logging
import socket
from urllib.parse import urlparse

import httpx

try:
    import truststore

    truststore.inject_into_ssl()
except ImportError:
    pass

from gsearch_mcp_cli.constants import ALLOWED_SCHEMES, BLOCKED_HOSTS, TIMEOUT_FETCH_HTTP
from gsearch_mcp_cli.exceptions import FetchError, SSRFBlockedError
from gsearch_mcp_cli.models import FetchResult

logger = logging.getLogger(__name__)


def validate_url(url: str) -> str:
    """Validate URL for SSRF attacks.

    Checks scheme, hostname, DNS resolution, and resolved IP against blocklists.
    Uses Python's ipaddress module for robust IP validation.

    Returns the validated URL string.
    Raises SSRFBlockedError if the URL is unsafe.
    """
    if not url:
        raise SSRFBlockedError("Empty URL")

    parsed = urlparse(url)

    if parsed.scheme not in ALLOWED_SCHEMES:
        raise SSRFBlockedError(f"Blocked scheme: {parsed.scheme or '(empty)'}")

    hostname = parsed.hostname
    if not hostname:
        raise SSRFBlockedError("No hostname in URL")

    if hostname in BLOCKED_HOSTS or hostname == "localhost":
        raise SSRFBlockedError(f"Blocked host: {hostname}")

    try:
        infos = socket.getaddrinfo(hostname, parsed.port or 443, proto=socket.IPPROTO_TCP)
    except socket.gaierror:
        raise SSRFBlockedError(f"DNS resolution failed: {hostname}")

    for family, _, _, _, sockaddr in infos:
        ip_str = sockaddr[0]
        try:
            ip = ipaddress.ip_address(ip_str)
        except ValueError:
            continue
        if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
            raise SSRFBlockedError(f"Resolved to private/reserved IP: {ip_str}")

    return url


async def fetch_page(
    url: str,
    format: str = "md",
    use_browser: str = "auto",
    browser_cdp=None,
    timeout: float = TIMEOUT_FETCH_HTTP,
) -> FetchResult:
    """Fetch a URL and return clean content.

    Args:
        url: URL to fetch (validated for SSRF).
        format: "md" for Markdown, "text" for plain text, "raw_html" for raw HTML.
        use_browser: "auto", "always", or "never".
        browser_cdp: BrowserCDP instance (required if use_browser != "never").
        timeout: Request timeout in seconds.
    """
    validated_url = validate_url(url)

    if use_browser == "always" and browser_cdp:
        html = await _fetch_with_browser(validated_url, browser_cdp, timeout)
    elif use_browser == "never" or browser_cdp is None:
        html = await _fetch_with_httpx(validated_url, timeout)
    else:
        try:
            html = await _fetch_with_httpx(validated_url, timeout)
        except FetchError:
            if browser_cdp:
                html = await _fetch_with_browser(validated_url, browser_cdp, timeout)
            else:
                raise

    if format == "raw_html":
        return FetchResult(
            url=validated_url,
            title=_extract_title(html),
            content=html,
            word_count=len(html.split()),
        )

    text_content = _extract_content(html)

    if format == "md":
        md_content = _to_markdown(html)
        return FetchResult(
            url=validated_url,
            title=_extract_title(html),
            content=md_content,
            word_count=len(md_content.split()),
        )

    return FetchResult(
        url=validated_url,
        title=_extract_title(html),
        content=text_content,
        word_count=len(text_content.split()),
    )


async def _fetch_with_httpx(url: str, timeout: float) -> str:
    """Fetch page HTML using httpx."""
    try:
        async with httpx.AsyncClient(follow_redirects=True) as client:
            resp = await client.get(
                url,
                timeout=timeout,
                headers={
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                    "AppleWebKit/537.36 (KHTML, like Gecko) "
                    "Chrome/120.0.0.0 Safari/537.36",
                },
            )
            resp.raise_for_status()
            return resp.text
    except httpx.HTTPStatusError as e:
        raise FetchError(f"HTTP {e.response.status_code} fetching {url}")
    except httpx.RequestError as e:
        raise FetchError(f"Request failed for {url}: {e}")


async def _fetch_with_browser(url: str, browser_cdp, timeout: float) -> str:
    """Fetch page HTML using headless browser (for JS-rendered content)."""
    async with browser_cdp.create_tab() as tab:
        await tab.navigate(url, timeout=timeout)
        html = await tab.evaluate("document.documentElement.outerHTML")
        return html or ""


def _extract_title(html: str) -> str:
    """Extract title from HTML."""
    try:
        import re

        match = re.search(r"<title[^>]*>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
        return match.group(1).strip() if match else ""
    except Exception:
        return ""


def _extract_content(html: str) -> str:
    """Extract main text content from HTML using trafilatura."""
    try:
        import trafilatura

        result = trafilatura.extract(html, include_links=True, include_tables=True)
        return result or ""
    except Exception:
        import re

        text = re.sub(r"<[^>]+>", " ", html)
        text = re.sub(r"\s+", " ", text).strip()
        return text


def _to_markdown(html: str) -> str:
    """Convert HTML to Markdown."""
    try:
        import trafilatura
        from html_to_markdown import convert

        main_html = trafilatura.extract(html, include_links=True, include_tables=True, output_format="html")
        if main_html:
            return convert(main_html)
        return convert(html)
    except Exception:
        return _extract_content(html)
