from __future__ import annotations

import asyncio
import html
import json
import re
from typing import Any
from urllib import error, parse, request
from urllib.parse import parse_qs, quote_plus, unquote, urlparse

from ..util import truncate_text

TAG_RE = re.compile(r"<[^>]+>")
RESULT_RE = re.compile(
    r'<a[^>]*class="result__a"[^>]*href="(?P<href>[^"]+)"[^>]*>(?P<title>.*?)</a>.*?(?:<a[^>]*class="result__snippet"[^>]*>(?P<snippet_a>.*?)</a>|<div[^>]*class="result__snippet"[^>]*>(?P<snippet_b>.*?)</div>)',
    re.S,
)


def _strip_tags(value: str) -> str:
    return html.unescape(TAG_RE.sub("", value)).strip()


def _normalize_duckduckgo_url(url: str) -> str:
    parsed = urlparse(url)
    if parsed.netloc.endswith("duckduckgo.com") and parsed.path.startswith("/l/"):
        query = parse_qs(parsed.query)
        if "uddg" in query:
            return unquote(query["uddg"][0])
    return url


def _http_request(url: str, *, method: str, headers: dict[str, str] | None = None, json_body: dict[str, Any] | None = None) -> tuple[int, dict[str, str], str]:
    payload = None
    merged_headers = dict(headers or {})
    if json_body is not None:
        merged_headers.setdefault("Content-Type", "application/json")
        payload = json.dumps(json_body, ensure_ascii=False).encode("utf-8")
    req = request.Request(url=url, method=method, headers=merged_headers, data=payload)
    with request.urlopen(req, timeout=30) as response:
        body = response.read().decode("utf-8", errors="replace")
        return response.status, dict(response.headers.items()), body


async def http_get_handler(context, arguments: dict[str, Any]) -> dict[str, Any]:
    max_chars = int(arguments.get("max_chars", 12000))
    status, headers, body = await asyncio.to_thread(
        _http_request,
        arguments["url"],
        method="GET",
        headers=arguments.get("headers") or {},
    )
    return {"status_code": status, "headers": headers, "text": truncate_text(body, max_chars)}


async def http_post_json_handler(context, arguments: dict[str, Any]) -> dict[str, Any]:
    max_chars = int(arguments.get("max_chars", 12000))
    status, headers, body = await asyncio.to_thread(
        _http_request,
        arguments["url"],
        method="POST",
        headers=arguments.get("headers") or {},
        json_body=arguments.get("json_body") or {},
    )
    return {"status_code": status, "headers": headers, "text": truncate_text(body, max_chars)}


async def web_search_handler(context, arguments: dict[str, Any]) -> dict[str, Any]:
    query = str(arguments["query"])
    max_results = int(arguments.get("max_results", 5))
    url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}"
    try:
        status, headers, body = await asyncio.to_thread(
            _http_request,
            url,
            method="GET",
            headers={
                "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123 Safari/537.36",
            },
        )
    except error.URLError as exc:
        return {"query": query, "results": [], "error": str(exc.reason)}
    results = []
    for match in RESULT_RE.finditer(body):
        href = _normalize_duckduckgo_url(match.group("href"))
        title = _strip_tags(match.group("title"))
        snippet = _strip_tags(match.group("snippet_a") or match.group("snippet_b") or "")
        if not title or not href:
            continue
        results.append({"title": title, "url": href, "snippet": snippet})
        if len(results) >= max_results:
            break
    return {
        "query": query,
        "status_code": status,
        "results": results,
        "raw_preview": truncate_text(body, 1500),
    }
