#!/usr/bin/env python3
"""Probe one declared GET resource per HTTP host without sending payment.

GET is selected from the catalog's own method metadata. POST, PATCH, DELETE,
HEAD-only, MCP and non-HTTP resources are excluded. Bodies are not retained.
"""

import argparse
import concurrent.futures
import datetime as dt
import json
import re
import socket
import urllib.error
import urllib.parse
import urllib.request

from scan_catalog import CATALOG, fetch_all


def pick_targets(items):
    targets = {}
    for item in items:
        if item.get("method") != "GET":
            continue
        url = item.get("resource") or ""
        parsed = urllib.parse.urlparse(url)
        if parsed.scheme not in {"http", "https"} or not parsed.hostname:
            continue
        old = targets.get(parsed.hostname)
        if old is None or item.get("lastUpdated", "") > old.get("lastUpdated", ""):
            targets[parsed.hostname] = item
    return targets


def render_url(item):
    url = item["resource"]
    params = (item.get("inputSchema") or {}).get("pathParams") or {}
    for key, value in params.items():
        replacement = urllib.parse.quote(str(value), safe="")
        url = url.replace(f":{key}", replacement).replace(f"{{{key}}}", replacement)
    if re.search(r"(?:/|^):[A-Za-z_][A-Za-z0-9_]*|\{[A-Za-z_][A-Za-z0-9_]*\}", url):
        return None
    return url


def probe(pair):
    host, item = pair
    url = render_url(item)
    if url is None:
        return {
            "host": host,
            "url": item["resource"],
            "catalog_last_updated": item.get("lastUpdated"),
            "status": None,
            "verdict": "template_unresolved",
            "seconds": 0,
            "error": "Catalog has a path template but no usable pathParams example.",
        }
    request = urllib.request.Request(
        url,
        headers={
            "User-Agent": "agentatwork-checkout-qa/1.0 (+https://agentatwork.xyz)",
            "Accept": "application/json, text/plain;q=0.5, */*;q=0.1",
        },
        method="GET",
    )
    started = dt.datetime.now(dt.timezone.utc)
    status = None
    headers = {}
    error = None
    try:
        with urllib.request.urlopen(request, timeout=10) as response:
            status = response.status
            headers = dict(response.headers.items())
            response.read(1)
    except urllib.error.HTTPError as exc:
        status = exc.code
        headers = dict(exc.headers.items())
    except (urllib.error.URLError, TimeoutError, socket.timeout, OSError) as exc:
        error = f"{type(exc).__name__}: {exc}"[:300]
    elapsed = (dt.datetime.now(dt.timezone.utc) - started).total_seconds()
    lower_headers = {key.lower(): value for key, value in headers.items()}
    if error:
        verdict = "network_error"
    elif status == 402 and any(key in lower_headers for key in ("payment-required", "x-payment-required", "www-authenticate")):
        verdict = "payable_402"
    elif status == 402:
        verdict = "bare_402"
    elif status is not None and 200 <= status < 400:
        verdict = "reachable_nonpaywall"
    elif status is not None and 400 <= status < 500:
        verdict = "client_error"
    else:
        verdict = "server_error"
    return {
        "host": host,
        "url": url,
        "catalog_last_updated": item.get("lastUpdated"),
        "status": status,
        "verdict": verdict,
        "seconds": round(elapsed, 3),
        "error": error,
    }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--out", required=True)
    parser.add_argument("--workers", type=int, default=24)
    args = parser.parse_args()
    targets = pick_targets(fetch_all())
    with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
        results = list(pool.map(probe, sorted(targets.items())))
    counts = {}
    for row in results:
        counts[row["verdict"]] = counts.get(row["verdict"], 0) + 1
    payload = {
        "measured_at": dt.datetime.now(dt.timezone.utc).isoformat().replace("+00:00", "Z"),
        "source": CATALOG,
        "method": "One unpaid declared-GET request per HTTP host; no POST/MCP routes invoked.",
        "targets": len(results),
        "verdicts": counts,
        "rows": results,
    }
    with open(args.out, "w", encoding="utf-8") as handle:
        json.dump(payload, handle, indent=2)
        handle.write("\n")


if __name__ == "__main__":
    main()
