#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Deterministic, dependency-free, fail-closed package checker."""
from __future__ import annotations

import hashlib
import json
import os
import re
import subprocess
import sys
from pathlib import Path, PurePosixPath

from conform import Failure, ROOT, SCHEMAS, build_report, canonical_bytes, digest, load_json, run_vectors, validate_document

LICENSE_SHA256 = "6ffb9af0a5ff2c17d0bbcb866634076f7bcd0f82ce756bd80856398ef67dd42a"
CANONICAL_MIT_BYTES = b"""MIT License

Copyright (c) 2026 Vaultfire contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
MANIFEST_EXCLUSIONS = {"ARTIFACTS.sha256", "reports/conformance-report.json"}
EPHEMERAL_DIRS = {".pytest_cache", "__pycache__"}
EXPECTED_COUNTS = {
    "supportedSourceModules": 15,
    "sdkDeployments": 4,
    "sdkWriteDeployments": 4,
    "sdkReadRoutes": 11,
    "generatedV3ApiRoutes": 20,
}
EXPECTED_ABI_COUNTS = {
    "accountabilityBond": 36,
    "partnershipBond": 55,
    "governance": 45,
    "mandateRegistry": 24,
}
EXPECTED_ABI_IDENTITIES = {
    "accountabilityBond": "0x0c18d05c868716fad4bfc1fcfad2da51c83f247de06a7e3871dcece768a9526f",
    "partnershipBond": "0x954aeee609e92a621e726dcae77be193085651626941d582ca2f7f3b7c073d8c",
    "governance": "0xcf81d69fed252f21bfd7661ff53868aa81b9ce1a4a1c89edbec9eaabffa7ad0a",
    "mandateRegistry": "0xe31e9d196bf5a37c328313f688b0d6eff881847e6c46437c1fc617faff8e80e3",
}
EXPECTED_GOVERNANCE_WRITES = {
    "acceptGuardian()",
    "cancel(uint256)",
    "cancelGuardianRotation()",
    "confirm(uint256)",
    "execute(uint256)",
    "guardianPause(address)",
    "propose(address,uint256,bytes)",
    "proposeGuardian(address)",
}
EXCLUDED_GOVERNANCE_CONFIGURATION_WRITES = {
    "addGuardianPauseTarget(address)",
    "pinCanonicalAddress(address,bytes32)",
    "setCallPolicy(address,bytes4,bool,uint96,bytes32)",
    "setSigners(address[],uint256)",
    "setTimelock(uint256)",
}
TRUTH_FALSE = ("audited", "deployed", "authorized", "endorsed")


def require(condition: bool, code: str, message: str) -> None:
    if condition is not True:
        raise Failure(code, message)


def sha256_file(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def verify_license(repo_root: Path | str, declared_digest: str) -> str:
    path = Path(repo_root) / "LICENSE"
    if path.is_symlink() or not path.is_file():
        raise Failure("VF3-LICENSE-001", "root LICENSE missing or not a regular file")
    content = path.read_bytes()
    observed = hashlib.sha256(content).hexdigest()
    if content != CANONICAL_MIT_BYTES:
        raise Failure("VF3-LICENSE-001", "root LICENSE differs from the selected canonical MIT text")
    if observed != LICENSE_SHA256 or declared_digest != observed:
        raise Failure("VF3-LICENSE-001", "root LICENSE digest is stale or mismatched")
    return observed


def safe_repo_file(repo: Path, relative: str) -> Path:
    require(isinstance(relative, str), "VF3-EVIDENCE-001", "source path must be a string")
    pure = PurePosixPath(relative)
    require(
        not pure.is_absolute() and "\\" not in relative and all(p not in ("", ".", "..") for p in pure.parts),
        "VF3-EVIDENCE-001",
        "unsafe source path",
    )
    candidate = repo
    for part in pure.parts:
        candidate = candidate / part
        require(not candidate.is_symlink(), "VF3-EVIDENCE-001", "source path component is a symlink")
    require(not candidate.is_symlink() and candidate.is_file(), "VF3-EVIDENCE-001", "source is missing or a symlink")
    resolved = candidate.resolve()
    try:
        resolved.relative_to(repo.resolve())
    except ValueError as exc:
        raise Failure("VF3-EVIDENCE-001", "source path escapes repository") from exc
    return candidate


def artifact_entries(release_mode: bool = False) -> list[tuple[str, str]]:
    entries: list[tuple[str, str]] = []
    for path in ROOT.rglob("*"):
        relative = path.relative_to(ROOT).as_posix()
        if any(part in EPHEMERAL_DIRS for part in path.relative_to(ROOT).parts) or relative.endswith((".pyc", ".pyo")):
            if release_mode:
                raise Failure("VF3-EVIDENCE-001", f"ephemeral cache prohibited in release mode: {relative}")
            continue
        if path.is_symlink():
            raise Failure("VF3-EVIDENCE-001", "package symlinks are prohibited")
        if not path.is_file():
            continue
        if relative in MANIFEST_EXCLUSIONS:
            continue
        entries.append((relative, sha256_file(path)))
    entries.append(("../LICENSE", sha256_file(ROOT.parent / "LICENSE")))
    return sorted(entries, key=lambda item: item[0].encode("utf-8"))


def expected_manifest(release_mode: bool = False) -> str:
    return "".join(f"{digest}  {path}\n" for path, digest in artifact_entries(release_mode))


def check_schemas() -> int:
    paths = sorted(SCHEMAS.glob("*.json"))
    require(len(paths) == 8, "VF3-SCHEMA-001", "exactly eight versioned schemas are required")
    ids: set[str] = set()
    for path in paths:
        schema = load_json(path)
        require(schema.get("$schema") == "https://json-schema.org/draft/2020-12/schema", "VF3-SCHEMA-001", "wrong schema dialect")
        require(schema.get("$id", "").endswith(path.name), "VF3-SCHEMA-001", "schema id/file mismatch")
        require(schema["$id"] not in ids, "VF3-SCHEMA-001", "duplicate schema id")
        ids.add(schema["$id"])
    return len(paths)


def check_registries(adversarial: dict) -> int:
    errors = load_json(ROOT / "registry/error-codes.v1.json")["errors"]
    codes = [item["code"] for item in errors]
    require(len(codes) == len(set(codes)) and len(codes) == 18 and "VF3-INTERNAL-001" not in {item["expectedError"] for item in adversarial["vectors"]}, "VF3-EVIDENCE-001", "error registry/vector boundary is incomplete or ambiguous")
    required = load_json(ROOT / "registry/conformance-checks.v1.json")["requiredChecks"]
    require(len(required) == 29 and len(required) == len(set(required)), "VF3-EVIDENCE-001", "mandatory check registry mismatch")
    compatibility = load_json(ROOT / "registry/compatibility.v1.json")
    require(compatibility.get("schemaVersion") == "1.0.0" and compatibility.get("canonicalization",{}).get("duplicateKeys") == "reject" and compatibility.get("extensions",{}).get("securityCriticalAllowed") is False and compatibility.get("extensions",{}).get("decisionInfluenceAllowed") is False and compatibility.get("deprecation",{}).get("v2FallbackProhibited") is True, "VF3-DEPRECATED-001", "compatibility policy weakened or version mismatched")
    require(set(item["expectedError"] for item in adversarial["vectors"]) <= set(codes), "VF3-EVIDENCE-001", "vector error is unregistered")
    return 3


def check_mapping() -> int:
    repo = ROOT.parent
    mapping = load_json(ROOT / "mappings/canonical-v3.json")
    snapshot = load_json(ROOT / mapping["interfaceSnapshot"]["path"].split("v3-open-spec/", 1)[1])
    require(mapping["counts"] == EXPECTED_COUNTS, "VF3-IDENTITY-001", "canonical interface count mismatch")
    snapshot_counts = {
        "supportedSourceModules": len(snapshot["supportedSourceModules"]),
        "sdkDeployments": len(snapshot["sdkDeployments"]),
        "sdkWriteDeployments": len(snapshot["sdkWriteMethods"]),
        "sdkReadRoutes": len(snapshot["sdkReadRoutes"]),
        "generatedV3ApiRoutes": len(snapshot["generatedV3ApiRoutes"]),
    }
    require(snapshot_counts == EXPECTED_COUNTS, "VF3-IDENTITY-001", "snapshot interface count mismatch")
    require(mapping.get("candidateCommitAuthority") == snapshot.get("candidateCommitAuthority") == "external-conformance-envelope-v1", "VF3-EVIDENCE-001", "candidate commit authority mismatch")
    source_transitions = {
        "Uninitialized>Proposed", "Proposed>Accepted", "Proposed>Expired", "Proposed>Cancelled",
        "Accepted>Funded", "Accepted>Cancelled", "Accepted>Expired", "Funded>Active",
        "Funded>Cancelled", "Funded>Expired", "Active>SettlementRequested", "Active>Expired",
        "SettlementRequested>Finalizable", "SettlementRequested>Challenged",
        "SettlementRequested>Cancelled", "SettlementRequested>Expired",
        "Finalizable>SettlementFrozen", "Finalizable>Cancelled", "Finalizable>Expired",
        "SettlementFrozen>Settled", "SettlementFrozen>Cancelled", "SettlementFrozen>Expired", "Challenged>Cancelled",
        "Challenged>SettlementFrozen", "Expired>ForceExited",
    }
    require(
        mapping.get("stateMachine") == snapshot.get("stateMachine")
        and len(snapshot["stateMachine"]["transitions"]) == 25
        and set(snapshot["stateMachine"]["transitions"]) == source_transitions,
        "VF3-IDENTITY-001",
        "canonical state transition closure mismatch",
    )
    verify_license(repo, mapping["license"]["sha256"])
    require(mapping["license"]["spdx"] == "MIT", "VF3-LICENSE-001", "SPDX binding mismatch")

    for binding in mapping["canonicalSources"].values():
        source = safe_repo_file(repo, binding["path"])
        require(sha256_file(source) == binding["sha256"], "VF3-EVIDENCE-001", f"source digest drift: {binding['path']}")

    require(len(mapping["supportedSourceModules"]) == 15, "VF3-IDENTITY-001", "source-module closure mismatch")
    require(len({item["path"] for item in mapping["supportedSourceModules"]}) == 15, "VF3-IDENTITY-001", "duplicate source module")
    require(len(mapping["sdkReadRoutes"]) == 11, "VF3-IDENTITY-001", "SDK route closure mismatch")
    require(len({item["route"] for item in mapping["sdkReadRoutes"]}) == 11, "VF3-IDENTITY-001", "duplicate SDK route")
    require(len(mapping["generatedV3ApiRoutes"]) == 20, "VF3-IDENTITY-001", "API route closure mismatch")
    require(len({(item["method"], item["path"]) for item in mapping["generatedV3ApiRoutes"]}) == 20, "VF3-IDENTITY-001", "duplicate API route")
    registry = load_json(repo / "v3-migration/completeness/supported-v3-modules.json")
    declared = {item["module"]: item for item in mapping["supportedSourceModules"]}
    authoritative = {item["module"]: item for item in registry["modules"]}
    require(set(declared) == set(authoritative) and len(authoritative) == 15, "VF3-IDENTITY-001", "supported source set omission or substitution")
    for name, item in authoritative.items():
        source = safe_repo_file(repo, item["path"])
        require(declared[name]["path"] == item["path"] and declared[name]["sha256"] == sha256_file(source), "VF3-EVIDENCE-001", f"supported source digest drift: {name}")
    norm = lambda p: re.sub(r":([A-Za-z][A-Za-z0-9_]*)", r"{\1}", p)
    expected_routes = {(x["method"], norm(x["path"])) for x in mapping["generatedV3ApiRoutes"]}
    route_registry = load_json(repo / "pr7/generated/route-registry.json")
    registry_routes = {(x["method"], norm(x["path"])) for x in route_registry["routes"] if x.get("version") == "v3"}
    openapi = load_json(repo / "pr7/generated/openapi.json")
    openapi_routes = {(method.upper(), norm(path)) for path, methods in openapi["paths"].items() for method, op in methods.items() if isinstance(op, dict) and op.get("x-protocol-version") == "v3"}
    require(expected_routes == registry_routes == openapi_routes and len(expected_routes) == 20, "VF3-IDENTITY-001", "canonical API route identity mismatch")

    deployments = mapping["sdkDeployments"]
    require({item["sdkName"]: item["signatureCount"] for item in deployments} == EXPECTED_ABI_COUNTS, "VF3-IDENTITY-001", "ABI signature counts mismatch")
    require({item["sdkName"]: item["abiIdentity"] for item in deployments} == EXPECTED_ABI_IDENTITIES, "VF3-IDENTITY-001", "ABI identities mismatch")
    for item in deployments:
        require(item["deploymentStatus"] == "not-deployed-not-authorized", "VF3-EVIDENCE-001", "unsupported deployment status")
        signatures = item["canonicalSignatures"]
        require(len(signatures) == item["signatureCount"] and len(signatures) == len(set(signatures)), "VF3-IDENTITY-001", "signature closure mismatch")
        peer = next((entry for entry in snapshot["sdkDeployments"] if entry["sdkName"] == item["sdkName"]), None)
        require(peer == item, "VF3-IDENTITY-001", "mapping/snapshot ABI mismatch")
    write_methods = mapping.get("sdkWriteMethods")
    require(write_methods == snapshot.get("sdkWriteMethods") and isinstance(write_methods, list) and len(write_methods) == 4, "VF3-IDENTITY-001", "SDK write closure mismatch")
    canonical_by_name = {item["sdkName"]: set(item["canonicalSignatures"]) for item in deployments}
    for item in write_methods:
        signatures = item.get("canonicalSignatures")
        require(
            item.get("sdkName") in canonical_by_name
            and isinstance(signatures, list)
            and item.get("methodCount") == len(signatures)
            and len(signatures) == len(set(signatures))
            and set(signatures).issubset(canonical_by_name[item["sdkName"]]),
            "VF3-IDENTITY-001",
            "invalid SDK write-method closure",
        )
    governance = next(item for item in deployments if item["sdkName"] == "governance")
    governance_writes = next(item for item in write_methods if item["sdkName"] == "governance")
    write_policy = {
        "canonicalAddressPinning": "only-self-append-only-codehash-bound",
        "canonicalPolicyMutationSignature": "setCallPolicy(address,bytes4,bool,uint96,bytes32)",
        "configurationWritesExposed": False,
        "excludedGovernanceConfigurationSignatures": sorted(EXCLUDED_GOVERNANCE_CONFIGURATION_WRITES),
    }
    require(
        set(governance_writes["canonicalSignatures"]) == EXPECTED_GOVERNANCE_WRITES
        and not EXCLUDED_GOVERNANCE_CONFIGURATION_WRITES.intersection(governance_writes["canonicalSignatures"])
        and mapping.get("sdkWritePolicy") == snapshot.get("sdkWritePolicy") == write_policy,
        "VF3-IDENTITY-001",
        "governance configuration writes exposed by SDK",
    )
    require(
        {"canonicalRuntimeCodehash(address)", "constructorPinnedPolicyTarget(address)", "pinCanonicalAddress(address,bytes32)", "setCallPolicy(address,bytes4,bool,uint96,bytes32)"}.issubset(governance["canonicalSignatures"])
        and "setCallPolicy(address,bytes4,bool,uint96)" not in governance["canonicalSignatures"],
        "VF3-IDENTITY-001",
        "governance codehash-bound ABI semantics mismatch",
    )
    partnership = next(item for item in deployments if item["sdkName"] == "partnershipBond")
    require(
        {"FLOURISHING_MISSION()", "FLOURISHING_NON_CONTROL_BOUNDARY()"}.issubset(partnership["canonicalSignatures"]),
        "VF3-IDENTITY-001",
        "partnership flourishing read constants missing",
    )
    accountability = next(item for item in deployments if item["sdkName"] == "accountabilityBond")
    retired = {
        "advanceToFinalizable(uint256)", "attestBreach(uint256,uint256,bytes32,uint96)",
        "breachSnapshot(uint256,uint256,bytes32)", "cancelStalledSettlement(uint256)",
        "challengeSettlement(uint256,uint256)", "disputeSubject(uint256)",
        "freezeSettlement(uint256,uint96)", "releaseDisputePayout(uint256,address,uint256)",
        "resolveChallenge(uint256)", "settle(uint256)",
    }
    timeout_policy = {
        "acceptedLiabilityTimeout": "settle-if-current-breach-otherwise-full-refund",
        "boundary": "activePeriodEnd",
        "callableWhilePaused": True,
        "canonicalRevalidation": "same-timestamp-before-settlement",
        "genericDisputeSelectors": "retired",
    }
    require(
        accountability["abiIdentity"] == "0x0c18d05c868716fad4bfc1fcfad2da51c83f247de06a7e3871dcece768a9526f"
        and accountability["signatureCount"] == 36
        and not retired.intersection(accountability["canonicalSignatures"]),
        "VF3-IDENTITY-001",
        "retired accountability selector or final ABI identity mismatch",
    )
    require(mapping["stateMachine"].get("accountabilityTimeout") == timeout_policy, "VF3-STATE-001", "accountability timeout policy mismatch")
    return 13 + len(mapping["canonicalSources"])


def check_vectors() -> tuple[int, int, int]:
    positive = load_json(ROOT / "vectors/positive.json")
    adversarial = load_json(ROOT / "vectors/adversarial.json")
    oracle = load_json(ROOT / "registry/vector-corpus.v1.json")
    require(oracle["positiveCount"] == 8 and oracle["adversarialCount"] == 38 and oracle["positiveIds"] == [x["id"] for x in positive["vectors"]] and oracle["adversarialIds"] == [x["id"] for x in adversarial["vectors"]] and oracle["positiveDigest"] == digest(positive["vectors"]) and oracle["adversarialDigest"] == digest(adversarial["vectors"]) and oracle["internalErrorExpectedProhibited"] is True, "VF3-INTERNAL-001", "canonical vector corpus registry mismatch")
    results = run_vectors({"positive": positive, "adversarial": adversarial})
    failures = [row for row in results if row[1] != "pass"]
    require(not failures, "VF3-INTERNAL-001", "one or more vectors did not produce the required result")
    return len(positive["vectors"]), len(adversarial["vectors"]), len(results)


def check_normative_text() -> int:
    spec = (ROOT / "spec/1.0.0/VF3-OPEN.md").read_text("utf-8")
    phrases = (
        "Flourishing cannot slash principal",
        "Rewards cannot consume collateral",
        "Surveillance data is never required or permitted for protected decisions",
        "Consent is explicit",
        "Exits and refunds are bounded and pause-safe",
        "does not imply that an implementation is audited, deployed, authorized, or endorsed",
        "module by module",
        "A monolithic, transitive, wildcard, inferred, or uncontrolled launch is prohibited",
        "Cross-chain trust-history portability",
        LICENSE_SHA256,
        "Open licensing grants",
    )
    for phrase in phrases:
        require(phrase in spec, "VF3-EVIDENCE-001", f"normative text missing: {phrase}")
    require("salt}" not in spec, "VF3-DOMAIN-001", "obsolete salted domain text remains")
    license_status = (ROOT / "LICENSE-STATUS.md").read_text("utf-8")
    require("RESOLVED" in license_status and "production decentralization" in license_status, "VF3-LICENSE-001", "license truth boundary missing")
    return len(phrases) + 1


def main() -> int:
    try:
        release_mode = "--release" in sys.argv
        positive = load_json(ROOT / "vectors/positive.json")
        adversarial = load_json(ROOT / "vectors/adversarial.json")
        manifest = ROOT / "ARTIFACTS.sha256"
        expected = expected_manifest(release_mode)
        if "--write" in sys.argv:
            manifest.write_text(expected, encoding="utf-8", newline="\n")
        else:
            require(manifest.is_file() and manifest.read_text("utf-8") == expected, "VF3-EVIDENCE-001", "artifact manifest drift")
        schema_count = check_schemas()
        registry_checks = check_registries(adversarial)
        mapping_checks = check_mapping()
        positive_count, adversarial_count, vector_checks = check_vectors()
        text_checks = check_normative_text()

        vector_results = run_vectors({"positive": positive, "adversarial": adversarial})
        report = build_report(vector_results)
        try:
            validate_document(report, production_current_time=True)
        except Failure as exc:
            require(exc.code == "VF3-EVIDENCE-001", exc.code, str(exc))
        require(report["summary"]["conformant"] is report["truth"]["specificationCompliant"], "VF3-EVIDENCE-001", "report conformance truth mismatch")
        require(all(report["truth"][key] is False for key in TRUTH_FALSE), "VF3-EVIDENCE-001", "conformance report overclaims truth")

        checks = schema_count + registry_checks + mapping_checks + vector_checks + text_checks + 3
        result = {
            "adversarialVectors": adversarial_count,
            "artifacts": len(artifact_entries()),
            "checks": checks,
            "positiveVectors": positive_count,
            "schemas": schema_count,
            "status": "pass" if report["summary"]["conformant"] else "blocked",
            "blocker": None if report["summary"]["conformant"] else "candidate checks remain failed; inspect external conformance report",
        }
        print(json.dumps(result, sort_keys=True))
        return 0 if report["summary"]["conformant"] else 2
    except Failure as exc:
        print(json.dumps({"code": exc.code, "error": str(exc), "status": "fail"}, sort_keys=True), file=sys.stderr)
        return 1
    except Exception:
        print(json.dumps({"code": "VF3-INTERNAL-001", "error": "fail-closed internal checker error", "status": "fail"}, sort_keys=True), file=sys.stderr)
        return 1


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