#!/usr/bin/env python3
"""Contract test for CARBON's per-page evidence capture, redaction, analysis, and report."""

from __future__ import annotations

import json
import subprocess
import sys
import tempfile
from pathlib import Path


PLUGIN_ROOT = Path(__file__).resolve().parents[1]
CARBON_SCRIPTS = PLUGIN_ROOT / "runtime" / "carbon" / "scripts"
sys.path.insert(0, str(CARBON_SCRIPTS))
from carbon_evidence import analyze_dom  # noqa: E402

RUN = CARBON_SCRIPTS / "carbon_run.py"
EVIDENCE = CARBON_SCRIPTS / "carbon_evidence.py"
PLAN = PLUGIN_ROOT / "runtime" / "carbon" / "references" / "execution" / "plan-template.json"


def invoke(script: Path, *args: str, ok: bool = True) -> subprocess.CompletedProcess[str]:
    result = subprocess.run([sys.executable, str(script), *args], text=True, capture_output=True, check=False)
    if ok and result.returncode:
        raise AssertionError(f"{script.name} {' '.join(args)} failed:\n{result.stderr}")
    if not ok and result.returncode == 0:
        raise AssertionError(f"{script.name} {' '.join(args)} unexpectedly succeeded")
    return result


with tempfile.TemporaryDirectory(prefix="carbon-evidence-") as temporary:
    root = Path(temporary)
    run_dir = Path(invoke(
        RUN, "init", "--root", str(root), "--plan", str(PLAN), "--mode", "quick",
        "--target", "https://example.test/account",
    ).stdout.strip())
    state = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
    test_id = state["tests"][0]["id"]

    screenshot = root / "page.svg"
    screenshot.write_text('<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360"><rect width="640" height="360" fill="#11131a"/><text x="40" y="80" fill="#fff">Account page evidence</text></svg>', encoding="utf-8")
    dom = root / "dom.html"
    dom.write_text('<html><body><img src="avatar.png"><input id="email"><div id="duplicate"></div><div id="duplicate"></div></body></html>', encoding="utf-8")
    console = root / "console.json"
    console.write_text(json.dumps([
        {"level": "error", "message": "Uncaught TypeError while loading profile"},
        {"level": "error", "message": "Failed to load resource: the server responded with a status of 404 (Not Found) /favicon.ico"},
        {"level": "info", "authorization": "Bearer abcdefghijklmnopqrstuvwxyz123"},
    ]), encoding="utf-8")
    network = root / "network.har"
    network.write_text(json.dumps({"log": {"entries": [{
        "request": {"url": "https://example.test/api/profile", "headers": [{"name": "Cookie", "value": "session=private"}]},
        "response": {"status": 503},
    }]}}), encoding="utf-8")
    accessibility = root / "a11y.json"
    accessibility.write_text(json.dumps({"violations": [{"id": "label", "impact": "serious", "description": "Form elements need labels"}]}), encoding="utf-8")
    performance = root / "performance.json"
    performance.write_text(json.dumps({"lcp": 4200, "cls": 0.18}), encoding="utf-8")
    data = root / "test-data.json"
    data.write_text(json.dumps({"email": "synthetic@example.test", "apiKey": "must-not-survive"}), encoding="utf-8")

    traversal = invoke(
        EVIDENCE, "page", "--run-dir", str(run_dir), "--page-id", "../../escape",
        "--url", "https://example.test/account", "--screenshot", str(screenshot), ok=False,
    )
    if "page id must use" not in traversal.stderr or (run_dir / "escape").exists():
        raise AssertionError("page evidence accepted a traversal-capable page id")

    result = invoke(
        EVIDENCE, "page", "--run-dir", str(run_dir), "--url", "https://example.test/account",
        "--title", "Account", "--state", "after profile load", "--viewport", "1440x900",
        "--visual-review", "The captured synthetic page is legible; no clipping or overlap is visible in this fixture.",
        "--test-id", test_id, "--screenshot", str(screenshot), "--dom", str(dom),
        "--console", str(console), "--network", str(network), "--accessibility", str(accessibility),
        "--performance", str(performance), "--data", str(data), "--unavailable", "video",
    )
    payload = json.loads(result.stdout)
    summary = payload["summary"]
    if summary["pages"] != 1 or summary["artifacts"] != 7 or summary["screenshots"] != 1:
        raise AssertionError(f"unexpected evidence summary: {summary}")
    if summary["consoleArtifacts"] != 1 or summary["networkArtifacts"] != 1:
        raise AssertionError("console/network evidence was not counted")

    manifest = json.loads((run_dir / "evidence" / "manifest.json").read_text(encoding="utf-8"))
    page = manifest["pages"][0]
    categories = {item["category"] for item in page["issues"]}
    required = {"console", "network", "dom", "accessibility", "performance", "correlation", "privacy"}
    if not required.issubset(categories):
        raise AssertionError(f"missing cross-artifact issue categories: {required - categories}")
    if page["evidenceGaps"] != ["video"]:
        raise AssertionError("unavailable evidence channel was not kept visible")
    console_issues = [item for item in page["issues"] if item["category"] == "console"]
    if not any(item["title"] == "Browser resources were not found" and item["severity"] == "low" for item in console_issues):
        raise AssertionError("favicon-style HTTP 404 console noise was not kept at low severity")
    if not any(item["title"] == "Browser runtime or page errors" and item["severity"] == "high" for item in console_issues):
        raise AssertionError("runtime page errors were not preserved as high severity")

    semantic_dom = run_dir / "semantic-dom.txt"
    semantic_dom.write_text(
        '- banner:\n  - navigation:\n    - link "Home"\n- main:\n  - textbox "Email"\n',
        encoding="utf-8",
    )
    semantic_issues = analyze_dom(
        run_dir,
        {"id": "SEMANTIC"},
        {"id": "SEMANTIC-DOM-01", "path": semantic_dom.relative_to(run_dir).as_posix()},
    )
    semantic_titles = {item["title"] for item in semantic_issues}
    if {"Document language is not declared", "Document title is missing"} & semantic_titles:
        raise AssertionError("semantic browser snapshot was misclassified as a complete HTML document")
    copied_text = "\n".join(
        (run_dir / item["path"]).read_text(encoding="utf-8", errors="replace")
        for item in page["artifacts"]
        if item["kind"] in {"console", "network", "data"}
    )
    if "abcdefghijklmnopqrstuvwxyz123" in copied_text or "session=private" in copied_text or "must-not-survive" in copied_text:
        raise AssertionError("sensitive telemetry survived evidence ingestion")

    updated_state = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
    matching = next(test for test in updated_state["tests"] if test["id"] == test_id)
    if not any("page evidence" in handle for handle in matching["evidence"]):
        raise AssertionError("page evidence was not connected to its test")

    report = (run_dir / "report.html").read_text(encoding="utf-8")
    required_copy = [
        "Evidence observatory", "Every tested page, every available channel",
        "General issue review across all collected artifacts", "Browser runtime or page errors",
        "Failed or server-error network activity", "evidence/pages/PAGE-001/screenshot-01.svg",
        "Confidence in quality", "Remaining risk", "Coverage missing", "What changes confidence",
    ]
    missing = [value for value in required_copy if value not in report]
    if missing:
        raise AssertionError(f"visual report is missing evidence surfaces: {missing}")

    invoke(RUN, "start", "--run-dir", str(run_dir))
    invoke(RUN, "update", "--run-dir", str(run_dir), "--test-id", test_id, "--status", "running")
    invoke(RUN, "update", "--run-dir", str(run_dir), "--test-id", test_id, "--status", "passed", "--actual", "Observed synthetic result")
    incomplete = invoke(RUN, "complete", "--run-dir", str(run_dir), ok=False)
    if "Evidence capture is incomplete" not in incomplete.stderr:
        raise AssertionError("browser-run completion did not fail closed on unaccounted evidence channels")
    invoke(
        EVIDENCE, "page", "--run-dir", str(run_dir), "--page-id", "PAGE-001",
        "--url", "https://example.test/account", "--account-missing",
        "--missing-reason", "The synthetic contract fixture captures only the channels created above.",
    )
    refreshed_manifest = json.loads((run_dir / "evidence" / "manifest.json").read_text(encoding="utf-8"))
    refreshed_page = refreshed_manifest["pages"][0]
    if refreshed_page["state"] != "after profile load" or refreshed_page["viewport"] != "1440x900":
        raise AssertionError("metadata-only evidence update overwrote the recorded page state or viewport")
    if set(refreshed_page["evidenceGaps"]) != {"trace", "storage", "video", "other"}:
        raise AssertionError("--account-missing did not explicitly account for every uncaptured channel")
    for persona_id, name in (("PERSONA-PRIMARY", "Primary user"), ("PERSONA-FIRST-TIME", "First-time user")):
        invoke(
            EVIDENCE, "persona", "--run-dir", str(run_dir), "--page-id", "PAGE-001",
            "--persona-id", persona_id, "--name", name, "--role", "account user",
            "--goal", "understand the account state", "--test-id", test_id,
            "--intent", "understand the account state", "--plan", "Inspect the account evidence", "--action", "Reviewed the account state and failure signals", "--outcome", "The account state was understandable from the available evidence.",
            "--rating", "joy=3", "--rating", "happiness=3", "--rating", "surprise=2", "--rating", "usability=3", "--positive", "Evidence is visible",
            "--recommendation", "Keep recovery beside the failure", "--confidence", "observed",
        )
        invoke(
            EVIDENCE, "explore", "--run-dir", str(run_dir), "--page-id", "PAGE-001", "--persona-id", persona_id,
            "--strategy", "breadth", "--charter-id", "CHARTER-BREADTH",
            "--charter", "Map changed and adjacent page states.", "--title", f"Account surface sampled by {name}",
            "--test-id", test_id, "--action", "Inspect the account page evidence",
            "--observation", "The account state and failure signals were sampled across available channels.",
        )
    invoke(RUN, "complete", "--run-dir", str(run_dir))
    completed = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
    if completed["status"] != "completed":
        raise AssertionError("run did not complete after every page channel was captured or accounted for")

print("PASS: every-page artifact capture, telemetry redaction, cross-artifact issue analysis, screenshot reporting, and evidence-to-test linkage verified.")
