#!/usr/bin/env python3
"""Non-submitting browser contract checks for buyer-facing links and forms."""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from urllib.parse import unquote, urljoin, urlparse

from playwright.sync_api import sync_playwright


def decoded(value: str) -> str:
    """Decode nested URL encoding without looping forever on malformed text."""
    current = value
    for _ in range(3):
        result = unquote(current)
        if result == current:
            break
        current = result
    return current


def one(page, selector: str):
    locator = page.locator(selector)
    count = locator.count()
    if count != 1:
        raise ValueError(f"selector {selector!r} matched {count} elements; expected 1")
    return locator


def run(spec: dict, override_url: str | None = None) -> tuple[dict, bool]:
    url = override_url or spec.get("url")
    if not isinstance(url, str) or not url.startswith(("http://", "https://")):
        raise ValueError("spec.url must be an http(s) URL")

    timeout_ms = int(spec.get("timeout_ms", 20_000))
    report: dict = {"url": url, "submitted": False, "fields": [], "checks": []}

    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(headless=True)
        page = browser.new_page()
        try:
            response = page.goto(url, wait_until="domcontentloaded", timeout=timeout_ms)
            report["http_status"] = response.status if response else None
            report["final_url"] = page.url

            values: dict[str, str] = {}
            for field in spec.get("fields", []):
                selector, value = field["selector"], str(field["value"])
                target = one(page, selector)
                target.fill(value)
                observed = target.input_value()
                values[selector] = observed
                report["fields"].append(
                    {"selector": selector, "value": observed, "matches_fixture": observed == value}
                )

            for check in spec.get("checks", []):
                kind, selector = check["kind"], check["selector"]
                result: dict = {"kind": kind, "selector": selector, "pass": False}
                try:
                    target = one(page, selector)
                    if kind == "inside_form":
                        present = target.evaluate("element => Boolean(element.closest('form'))")
                        result.update(pass_=present, observed=present)
                    elif kind == "href_includes_fields":
                        href = target.get_attribute("href") or ""
                        readable = decoded(href)
                        wanted = [values[s] for s in check.get("fields", [])]
                        missing = [value for value in wanted if value not in readable]
                        result.update(pass_=not missing, href=href, missing_values=missing)
                    elif kind == "href_path":
                        href = target.get_attribute("href") or ""
                        resolved = urljoin(page.url, href)
                        observed = urlparse(resolved).path
                        expected = check["equals"]
                        result.update(pass_=observed == expected, href=href,
                                      observed=observed, expected=expected)
                    elif kind == "href_not_contains":
                        href = target.get_attribute("href") or ""
                        readable = decoded(href)
                        forbidden = [str(x) for x in check.get("values", [])]
                        found = [value for value in forbidden if value in readable]
                        result.update(pass_=not found, href=href, found=found)
                    else:
                        raise ValueError(f"unknown check kind: {kind}")
                except Exception as error:
                    result["error"] = str(error)
                if "pass_" in result:
                    result["pass"] = result.pop("pass_")
                report["checks"].append(result)
        finally:
            browser.close()

    fields_ok = all(row["matches_fixture"] for row in report["fields"])
    checks_ok = bool(report["checks"]) and all(row["pass"] for row in report["checks"])
    report["pass"] = fields_ok and checks_ok
    return report, report["pass"]


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Fill synthetic browser fields and inspect links without clicking or submitting."
    )
    parser.add_argument("spec", type=Path, help="JSON contract specification")
    parser.add_argument("--url", help="override spec.url (useful for local fixtures)")
    args = parser.parse_args()
    try:
        spec = json.loads(args.spec.read_text())
        report, passed = run(spec, args.url)
        print(json.dumps(report, indent=2, sort_keys=True))
        return 0 if passed else 1
    except Exception as error:
        print(json.dumps({"pass": False, "probe_error": str(error)}, sort_keys=True))
        return 2


if __name__ == "__main__":
    raise SystemExit(main())
