#!/usr/bin/env python3
"""End-to-end contract test for CARBON's automatic framework manager."""

from __future__ import annotations

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


PLUGIN = Path(__file__).resolve().parents[1]
FRAMEWORK = PLUGIN / "runtime" / "carbon" / "scripts" / "carbon_framework.py"
RUNNER = PLUGIN / "runtime" / "carbon" / "scripts" / "carbon_run.py"


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


def write(path: Path, text: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")


def main() -> int:
    with tempfile.TemporaryDirectory(prefix="carbon-framework-") as temporary:
        root = Path(temporary)
        write(
            root / "src" / "api.py",
            "from flask import Flask\napp = Flask(__name__)\n@app.get('/api/widgets')\ndef widgets(): return []\n",
        )
        write(
            root / "src" / "WidgetPage.tsx",
            "export function WidgetPage(){ const createWidget=()=>{}; const saveWidget=()=>{}; const deleteWidget=()=>{}; return <button onClick={() => fetch('/api/widgets')}>Load widgets</button> }\n",
        )
        write(root / "tests" / "widget.spec.ts", "test('loads widgets', async () => {})\n")
        openapi = {
            "openapi": "3.0.3",
            "info": {"title": "Widget API", "version": "1"},
            "paths": {
                "/api/widgets": {
                    "get": {
                        "operationId": "getWidgets",
                        "x-carbon-ui-flows": ["FLOW-widgets"],
                        "responses": {"200": {"description": "ok"}, "401": {"description": "denied"}},
                    }
                },
                "/api/widgets/{id}": {
                    "patch": {
                        "operationId": "updateWidget",
                        "security": [{"bearer": []}],
                        "requestBody": {
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "type": "object",
                                        "required": ["name"],
                                        "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 40}},
                                    }
                                }
                            }
                        },
                        "responses": {"200": {"description": "ok"}, "400": {"description": "bad"}},
                    }
                },
            },
        }
        write(root / "openapi.json", json.dumps(openapi))
        har = {
            "log": {
                "entries": [
                    {
                        "pageref": "page_widgets",
                        "request": {"method": "GET", "url": "https://local.test/api/widgets"},
                        "response": {"status": 200},
                        "time": 42,
                    }
                ],
                "pages": [{"id": "page_widgets", "title": "Widget flow"}],
            }
        }
        write(root / "widgets.har", json.dumps(har))
        run("git", "init", "-q", str(root))
        run("git", "-C", str(root), "config", "user.email", "carbon-contract@example.invalid")
        run("git", "-C", str(root), "config", "user.name", "CARBON Contract")
        run("git", "-C", str(root), "add", ".")
        run("git", "-C", str(root), "commit", "-qm", "initial fixture")
        write(
            root / "src" / "WidgetPage.tsx",
            "export function WidgetPage(){ const createWidget=()=>{}; const saveWidget=()=>{}; const deleteWidget=()=>{}; return <button onClick={() => fetch('/api/widgets').catch(() => 'error')}>Load widgets</button> }\n",
        )
        run("git", "-C", str(root), "add", "src/WidgetPage.tsx")
        run("git", "-C", str(root), "commit", "-qm", "change widget flow")

        prepared = run(
            sys.executable, str(FRAMEWORK), "prepare", "--root", str(root),
            "--request", "test this", "--target", "https://local.test", "--size", "quick",
            "--openapi", str(root / "openapi.json"), "--har", str(root / "widgets.har"),
        )
        prepared_json = json.loads(prepared.stdout)
        assert "direct-http:openapi" in prepared_json["selectedAdapters"]
        base = root / ".carbon" / "framework"
        model = json.loads((base / "product-model.json").read_text())
        plan = json.loads((base / "orchestration-plan.json").read_text())
        candidate_pool = json.loads((base / "candidate-pool.json").read_text())
        assert candidate_pool["schema"] == "carbon.candidate-pool/v1"
        assert len(candidate_pool["tests"]) >= 80
        assert plan["coverageProfile"] == "broad"
        assert plan["businessValue"]["candidateCount"] == 5
        assert plan["businessValue"]["selectedTestIds"]
        assert len(plan["tests"]) <= 20
        assert plan["estimatedMinutes"] <= 25
        assert plan["generatedCandidates"] == len(candidate_pool["tests"])
        assert plan["deferredCandidates"] == len(candidate_pool["tests"]) - len(plan["tests"])
        assert all(plan["coverageByDomain"].get(domain, 0) >= 5 for domain in (
            "functionality", "ui", "ux", "usability", "accessibility", "privacy", "security",
            "performance", "networking", "reliability", "compatibility", "content", "data", "api",
            "localization", "business-value",
        ))
        more_result = json.loads(run(
            sys.executable, str(FRAMEWORK), "more", "--root", str(root),
            "--limit", "12", "--budget-minutes", "20",
        ).stdout)
        more_plan = json.loads(Path(more_result["plan"]).read_text())
        assert more_result["schema"] == "carbon.more-coverage/v1"
        assert 1 <= len(more_plan["tests"]) <= 12
        assert not ({row["id"] for row in plan["tests"]} & {row["id"] for row in more_plan["tests"]})
        assert more_result["remainingCandidates"] < plan["deferredCandidates"]
        second_more_result = json.loads(run(
            sys.executable, str(FRAMEWORK), "more", "--root", str(root),
            "--limit", "12", "--budget-minutes", "20",
        ).stdout)
        second_more_plan = json.loads(Path(second_more_result["plan"]).read_text())
        first_more_ids = {row["id"] for row in more_plan["tests"]}
        second_more_ids = {row["id"] for row in second_more_plan["tests"]}
        assert not first_more_ids & second_more_ids
        assert second_more_result["remainingCandidates"] < more_result["remainingCandidates"]
        assert len(model["apis"]) >= 2
        assert model["flows"]
        inferred_crud = next(
            row for row in model["flows"]
            if row.get("key") == "create-edit-delete-record"
        )
        assert inferred_crud["evidence"] == "source-inference"
        widget_surface = next(
            row for row in model["surfaces"]
            if row.get("source") == "src/WidgetPage.tsx"
        )
        assert inferred_crud["id"] in widget_surface["flowIds"]
        assert "src/WidgetPage.tsx" in model["changedFiles"]
        assert "tests/widget.spec.ts" in model["testFiles"]
        run(sys.executable, str(FRAMEWORK), "api-generate", "--root", str(root), "--spec", str(root / "openapi.json"))
        suite = json.loads((base / "regression-suite.json").read_text())
        categories = {row["type"] for row in suite["tests"] if row.get("apiOperation")}
        assert {"api-positive", "api-negative", "api-boundary", "api-authorization", "api-idempotency", "api-resilience"} <= categories
        assert any(row.get("flowIds") for row in suite["tests"] if row.get("apiOperation") == "getWidgets")
        assert plan["tests"] and plan["availableAdapters"]
        assert "carbon-browser/v1" in plan["availableAdapters"]
        assert plan["browserContract"]["protocol"] == "carbon-browser/v1"
        assert plan["browserContract"]["negotiationRequired"] is True
        assert "capture.screenshot" in plan["browserContract"]["requiredOperations"]
        assert "screenshot" in plan["browserContract"]["requiredEvidence"]
        assert prepared_json["browserContract"] == plan["browserContract"]
        assert plan["testStrategy"]["selected"] == "breadth"
        assert len(plan["personas"]) == 2
        assert [row["id"] for row in plan["explorationCharters"]] == ["CHARTER-BREADTH"]
        assert plan["testingDoctrine"]["schema"] == "carbon.testing-doctrine/v1"
        assert "rapid-and-session-based-exploration" in plan["testingDoctrine"]["practiceBalances"]
        assert prepared_json["testingDoctrineVersion"] == plan["testingDoctrine"]["version"]
        assert plan["projectMemory"]["schema"] == "carbon.project-memory/v1"
        assert plan["projectMemory"]["freshness"] in {"initialized", "current", "stale", "aging"}
        assert prepared_json["projectMemoryFreshness"] == plan["projectMemory"]["freshness"]

        candidate = "API-GETWIDGETS-POS"
        auth_case = "API-GETWIDGETS-AUTH"
        for run_id in ("contract-build-a", "contract-build-b"):
            run_dir = root / ".carbon" / "runs" / run_id
            run(
                sys.executable, str(RUNNER), "init", "--root", str(root),
                "--plan", str(base / "orchestration-plan.json"), "--mode", "quick",
                "--target", "https://local.test", "--run-id", run_id,
            )
            run(sys.executable, str(RUNNER), "start", "--run-dir", str(run_dir))
            run(sys.executable, str(RUNNER), "update", "--run-dir", str(run_dir), "--test-id", candidate, "--status", "running")
            run(
                sys.executable, str(RUNNER), "update", "--run-dir", str(run_dir),
                "--test-id", candidate, "--status", "passed", "--actual", "200 and linked UI list rendered",
                "--evidence", f"evidence/{run_id}-widgets.json",
            )
            run(
                sys.executable, str(FRAMEWORK), "observe", "--root", str(root),
                "--test-id", candidate, "--run-dir", str(run_dir),
            )

        second_run = root / ".carbon" / "runs" / "contract-build-b"
        run(sys.executable, str(RUNNER), "update", "--run-dir", str(second_run), "--test-id", auth_case, "--status", "running")
        run(
            sys.executable, str(RUNNER), "update", "--run-dir", str(second_run),
            "--test-id", auth_case, "--status", "failed", "--actual", "wrong-owner response exposed widget name",
            "--evidence", "evidence/auth-cross-owner.json",
        )
        run(
            sys.executable, str(RUNNER), "finding", "--run-dir", str(second_run),
            "--severity", "high", "--title", "Cross-owner widget disclosure", "--detail", "Wrong-owner access returned data",
            "--test-id", auth_case, "--starting-state", "two isolated tenant fixtures", "--steps", "request widget as wrong owner",
            "--expected", "404 or policy-safe denial", "--actual", "200 with widget name", "--evidence", "evidence/auth-cross-owner.json",
            "--code-location", "src/api.py", "--regression-test", auth_case,
        )
        repro = second_run / "repro" / "F-001" / "repro.json"
        assert repro.exists() and json.loads(repro.read_text())["steps"]

        run(sys.executable, str(FRAMEWORK), "promote", "--root", str(root), "--test-id", candidate, ok=False)
        promoted = run(sys.executable, str(FRAMEWORK), "promote", "--root", str(root), "--test-id", candidate, "--confirm")
        assert json.loads(promoted.stdout)["lifecycle"] == "regression"
        run(
            sys.executable, str(FRAMEWORK), "outcome", "--root", str(root), "--test-id", auth_case,
            "--disposition", "escaped", "--note", "Matched a confirmed production authorization defect", "--run-id", "contract-build-b",
        )
        adaptive_path = Path(run(sys.executable, str(FRAMEWORK), "adapt", "--root", str(root), "--run-dir", str(second_run)).stdout.strip())
        adaptive = json.loads(adaptive_path.read_text())
        assert adaptive["tests"][0]["id"].startswith("REPRO-")

        analysis = json.loads(run(sys.executable, str(FRAMEWORK), "analyze", "--root", str(root)).stdout)
        assert analysis["releaseRecommendation"] == "hold"
        assert analysis["regressionLifecycle"]["regression"] >= 1
        assert analysis["statisticalComparison"] is not None
        assert analysis["qualityConfidence"]["confidenceInQuality"]["band"] == "low"
        assert analysis["remainingRisk"]["level"] in {"high", "critical"}
        assert isinstance(analysis["coverageMissing"], list)
        assert analysis["whatWouldChangeConfidence"]
        report_path = Path(run(sys.executable, str(FRAMEWORK), "report", "--root", str(root)).stdout.strip())
        report = report_path.read_text(encoding="utf-8")
        assert "CARBON" in report
        assert "ENGINEERING CONFIDENCE" in report
        assert "Automatic framework" in report
        assert "Regression lifecycle" in report
        assert "Executed page evidence" in report
        assert "Persona feedback and exploratory coverage" in report
        assert "Confidence in quality" in report
        assert "Remaining risk" in report
        assert "What changes confidence" in report
        assert "viewport" in report and "@media(max-width:640px)" in report

        preview = json.loads(run(sys.executable, str(FRAMEWORK), "ci", "--root", str(root)).stdout)
        assert preview["applied"] == "false" and "pull_request" in preview["workflowPreview"]
        applied = json.loads(run(sys.executable, str(FRAMEWORK), "ci", "--root", str(root), "--apply").stdout)
        assert applied["applied"] == "true"
        assert (root / ".github" / "workflows" / "carbon.yml").exists()
        assert (root / "tools" / "carbon" / "carbon_framework.py").exists()

        explicit_depth = json.loads(run(
            sys.executable, str(FRAMEWORK), "prepare", "--root", str(root),
            "--request", "drill into the widget flow", "--target", "https://local.test",
            "--size", "quick", "--strategy", "depth",
        ).stdout)
        assert explicit_depth["testStrategy"]["selected"] == "depth"
        depth_plan = json.loads((base / "orchestration-plan.json").read_text())
        assert [row["id"] for row in depth_plan["explorationCharters"]] == ["CHARTER-DEPTH"]

        openapi["paths"]["/api/widgets"]["get"]["x-carbon-ui-flows"] = ["FLOW-widgets-v2"]
        write(root / "openapi.json", json.dumps(openapi))
        run(sys.executable, str(FRAMEWORK), "api-generate", "--root", str(root), "--spec", str(root / "openapi.json"))
        refreshed = json.loads((base / "regression-suite.json").read_text())
        stale = next(row for row in refreshed["tests"] if row["id"] == candidate)
        assert stale["lifecycle"] == "needs-review" and stale["staleReason"]

    with tempfile.TemporaryDirectory(prefix="carbon-api-only-") as temporary:
        api_root = Path(temporary)
        write(
            api_root / "src" / "routes.js",
            "import { validateBook } from './validate.js';\nrouter.get('/api/books', listBooks);\nrouter.post('/api/books', createBook);\nrouter.delete('/api/books/:id', deleteBook);\n",
        )
        write(
            api_root / "src" / "validate.js",
            "export function validateBook(body) { if (typeof body.title !== 'string') throw new Error('title must be a string'); if (body.title.length > 200) throw new Error('title too long'); }\n",
        )
        write(
            api_root / "src" / "dom.js",
            "export function render(){ document.createElement('div'); localStorage.removeItem('x'); saveMessages([]); }\n",
        )
        write(
            api_root / "test" / "routes.test.js",
            "router.get('/api/books', () => { throw new Error('test double'); });\n",
        )
        api_prepared = json.loads(run(
            sys.executable, str(FRAMEWORK), "prepare", "--root", str(api_root),
            "--request", "test the API", "--target", "http://127.0.0.1:3000",
            "--size", "quick",
        ).stdout)
        api_model = json.loads((api_root / ".carbon" / "framework" / "product-model.json").read_text())
        api_plan = json.loads((api_root / ".carbon" / "framework" / "orchestration-plan.json").read_text())
        assert len(api_model["apis"]) == 3
        assert all(row["source"] == "src/routes.js" for row in api_model["apis"])
        assert not any(row.get("key") == "create-edit-delete-record" for row in api_model["flows"])
        assert len(api_prepared["affectedApiIds"]) == 3
        assert "direct-http:source" in api_prepared["selectedAdapters"]
        assert {"api-positive", "api-negative", "api-boundary", "api-authorization", "api-idempotency", "api-resilience"} <= {row["type"] for row in api_plan["tests"]}
        post_api = next(row for row in api_model["apis"] if row["method"] == "POST")
        assert any("title.length > 200" in row for row in post_api["codeContracts"])
        post_boundary = next(
            row for row in api_plan["tests"]
            if row["type"] == "api-boundary" and row["apiOperation"] == "post-/api/books"
        )
        assert any("title.length > 200" in step for step in post_boundary["steps"])

    print("CARBON automatic framework contract: PASS")
    return 0


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