from __future__ import annotations

import base64
import copy
import hashlib
import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import check_organizational_readiness as c
import strict_json_schema as s


class FifthGenerationAuditRegressions(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.base = s.load_json(ROOT / "manifests/organizational-readiness.template.json")

    def reject(self, fn):
        with self.assertRaises(Exception):
            fn()

    def staged(self, mutation=None):
        with tempfile.TemporaryDirectory() as td:
            repo = Path(td)
            subprocess.run(["git", "init", "-q", str(repo)], check=True)
            subprocess.run(["git", "-C", str(repo), "config", "user.name", "fixture"], check=True)
            subprocess.run(["git", "-C", str(repo), "config", "user.email", "fixture@example.invalid"], check=True)
            (repo / "README").write_text("source\n")
            subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
            subprocess.run(["git", "-C", str(repo), "commit", "-q", "-m", "source"], check=True)
            source = subprocess.check_output(["git", "-C", str(repo), "rev-parse", "HEAD"], text=True).strip()
            selection_path = repo / "pr10/partner-package/evidence/organizational/privacy-inputs/selection.json"
            selection_path.parent.mkdir(parents=True)
            selection_path.write_text('{"phase":"selection"}\n')
            subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
            subprocess.run(["git", "-C", str(repo), "commit", "-q", "-m", "selection"], check=True)
            selection = subprocess.check_output(["git", "-C", str(repo), "rev-parse", "HEAD"], text=True).strip()
            evidence_path = repo / c.ARTIFACT_MANIFEST_PATH
            evidence_path.write_text('{"phase":2}\n')
            subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
            subprocess.run(["git", "-C", str(repo), "commit", "-q", "-m", "evidence"], check=True)
            evidence = subprocess.check_output(["git", "-C", str(repo), "rev-parse", "HEAD"], text=True).strip()
            envelope = repo / c.POST_COMMIT_ENVELOPE_PATH
            envelope.write_text(json.dumps({"evidenceCommit": evidence}) + "\n")
            subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
            subprocess.run(["git", "-C", str(repo), "commit", "-q", "-m", "attestation"], check=True)
            if mutation == "dirty":
                (repo / "dirty").write_text("x")
            elif mutation == "extra":
                (repo / "extra").write_text("x")
                subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
                subprocess.run(["git", "-C", str(repo), "commit", "-q", "-m", "extra"], check=True)
            elif mutation == "path":
                envelope = repo / "wrong.json"; envelope.write_text("{}")
            return c.validate_staged_git_flow(
                repo, source, selection, evidence, envelope
            )

    def signature_fixture(self):
        manifest = copy.deepcopy(self.base)
        subject = "organization:" + "1" * 64; partner = "organization:" + "2" * 64
        manifest["subjectOrganizationId"] = subject; manifest["partnerOrganizationId"] = partner
        roles = ["release-manager", "independent-reviewer", "authorized-signatory", "partner-authorized-signatory"]
        identities = {}; assigned = {}
        for index, role in enumerate(roles, 1):
            public = bytes([index]) * 32; identity_id = "identity:" + hashlib.sha256(public).hexdigest()
            assigned[role] = identity_id
            identities[identity_id] = {
                "_public": public, "allowedRoles": [role],
                "organizationId": partner if role == "partner-authorized-signatory" else subject,
                "authorityEvidenceId": "PARTNER-ACCEPTANCE" if role == "partner-authorized-signatory" else c.ROLE_EVIDENCE[role],
            }
        for row in manifest["roles"]:
            if row["roleId"] in assigned: row["identityId"] = assigned[row["roleId"]]
        manifest["attestation"]["partnerAcceptedByIdentityId"] = assigned["partner-authorized-signatory"]
        candidate = "sha256:" + "c" * 64
        payload = c.signature_payload(candidate, subject, partner, roles)
        payload_sha = hashlib.sha256(payload).hexdigest()
        signers = []
        for index, role in enumerate(roles, 1):
            identity_id = assigned[role]; identity = identities[identity_id]
            signers.append({
                "identityId": identity_id, "organizationId": identity["organizationId"], "roleId": role,
                "keyType": "Ed25519", "publicKeySha256": hashlib.sha256(identity["_public"]).hexdigest(),
                "authorityEvidenceId": identity["authorityEvidenceId"], "signedPayloadSha256": payload_sha,
                "signatureBase64": base64.b64encode(bytes([index]) * 64).decode(), "verified": True,
            })
        document = {
            "schemaVersion": "2.0.0", "recordType": "candidate-signature-set",
            "candidateId": candidate, "organizationIds": sorted([subject, partner]),
            "threshold": {"required": 4, "total": 4}, "signers": signers, "allVerified": True,
        }
        return manifest, candidate, identities, document

    def signature_call(self, mutator=None):
        manifest, candidate, identities, document = self.signature_fixture()
        if mutator: mutator(manifest, identities, document)
        old = c.verify_ed25519
        try:
            c.verify_ed25519 = lambda *_: True
            return c.validate_signature_set(document, manifest, candidate, identities, {})
        finally:
            c.verify_ed25519 = old

    def artifact_probe(self, evidence_id, special):
        candidate = "sha256:" + "c" * 64; scope = "d" * 64
        manifest = copy.deepcopy(self.base)
        manifest["subjectOrganizationId"] = "organization:" + "1" * 64
        manifest["partnerOrganizationId"] = "organization:" + "2" * 64
        manifest["releaseCandidate"].update(sourceCommit="a" * 40, gitTree="b" * 40, evidenceCommit="e" * 40)
        true_keys = {
            "channelsControlTested", "requiredFunctionsCovered", "subjectExecuted", "partnerExecuted",
            "intakeRouteControlTested", "coverageVerified", "staffingVerified", "responsibilitiesAccepted",
            "maximumLossBounded", "residualLiabilityAccepted", "unlinkabilityVerified", "crossChainValueFlowAbsent",
        }
        facts = {}
        for key in c.ARTIFACT_FACT_KEYS[evidence_id]:
            facts[key] = hashlib.sha256((evidence_id + key).encode()).hexdigest() if key.endswith("Sha256") else (True if key in true_keys else "typed-value")
        refs = {key: {"path": f"virtual/{evidence_id}/{key}.json", "sha256": value} for key, value in facts.items() if key.endswith("Sha256")}
        artifact = {
            "schemaVersion": "1.0.0", "artifactId": evidence_id,
            "artifactType": c.EVIDENCE_SPECS[evidence_id]["claim"], "candidateId": candidate,
            "sourceCommit": "a" * 40, "subjectOrganizationId": manifest["subjectOrganizationId"],
            "partnerOrganizationId": manifest["partnerOrganizationId"], "scopeSha256": scope,
            "facts": facts, "blobRefs": refs,
        }
        resolved = {refs["sourceRecordSha256"]["path"]: {
            "schemaVersion": "1.0.0", "recordType": c.EVIDENCE_SPECS[evidence_id]["claim"],
            "candidateId": candidate, "subjectOrganizationId": manifest["subjectOrganizationId"],
            "partnerOrganizationId": manifest["partnerOrganizationId"], "evidenceId": evidence_id,
            "assertions": {key: value for key, value in facts.items() if not key.endswith("Sha256")},
        }}
        for key, ref in refs.items(): resolved.setdefault(ref["path"], {"garbage": True})
        for key, value in special.items(): resolved[refs[key]["path"]] = value
        old = c.safe_repo_file, c.require_tracked_exact, c.digest, c.load_json
        try:
            c.safe_repo_file = lambda p, *a, **k: Path(p)
            c.require_tracked_exact = lambda *a, **k: None
            c.digest = lambda p: next(ref["sha256"] for ref in refs.values() if ref["path"] == str(p))
            c.load_json = lambda p: resolved[str(p)] if str(p) in resolved else s.load_json(Path(p))
            return c.validate_objective_artifact(artifact, evidence_id, manifest, candidate, scope)
        finally:
            c.safe_repo_file, c.require_tracked_exact, c.digest, c.load_json = old

    def test_fifth_01_duplicate_key(self): self.reject(lambda: s.loads_json('{"x":1,"x":2}'))
    def test_fifth_02_nan(self): self.reject(lambda: s.loads_json('{"x":NaN}'))
    def test_fifth_03_infinity(self): self.reject(lambda: s.loads_json('{"x":Infinity}'))
    def test_fifth_04_negative_infinity(self): self.reject(lambda: s.loads_json('{"x":-Infinity}'))
    def test_fifth_05_trailing_polyglot(self): self.reject(lambda: s.loads_json('{"x":1}{"y":2}'))
    def test_fifth_06_bom(self): self.reject(lambda: s.loads_json('\ufeff{"x":1}'))
    def test_fifth_07_bool_not_integer(self): self.reject(lambda: s.validate(True, {"$schema": "https://json-schema.org/draft/2020-12/schema", "type": "integer"}))
    def test_fifth_08_integer_not_boolean(self): self.reject(lambda: s.validate(1, {"$schema": "https://json-schema.org/draft/2020-12/schema", "type": "boolean"}))
    def test_fifth_09_float_not_integer(self): self.reject(lambda: s.validate(1.0, {"$schema": "https://json-schema.org/draft/2020-12/schema", "type": "integer"}))
    def test_fifth_10_unknown_schema_keyword(self): self.reject(lambda: s.validate({}, {"$schema": "https://json-schema.org/draft/2020-12/schema", "unknownAssertion": True}))
    def test_fifth_11_temp_git_positive(self): self.staged()
    def test_fifth_12_temp_git_dirty(self): self.reject(lambda: self.staged("dirty"))
    def test_fifth_13_temp_git_extra_commit(self): self.reject(lambda: self.staged("extra"))
    def test_fifth_14_temp_git_wrong_envelope(self): self.reject(lambda: self.staged("path"))
    def test_fifth_15_phase_two_has_no_self_binding(self): self.assertFalse(set().union(*c.ARTIFACT_FACT_KEYS.values()) & {"evidenceCommit", "evidenceTree", "artifactManifestSha256", "attestationCommit", "attestationTree"})
    def test_fifth_16_post_commit_omits_own_commit(self): self.assertNotIn('"attestationCommit"', (ROOT / "scripts/check_organizational_readiness.py").read_text())
    def test_fifth_17_disabled_trust_root(self): self.reject(c.load_trust)
    def test_fifth_18_signature_positive(self): self.signature_call()
    def test_fifth_19_signature_wrong_org(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][0].__setitem__("organizationId", m["partnerOrganizationId"])))
    def test_fifth_20_signature_wrong_identity(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][0].__setitem__("identityId", d["signers"][1]["identityId"])))
    def test_fifth_21_signature_wrong_role(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][0].__setitem__("roleId", "independent-reviewer")))
    def test_fifth_22_signature_weak_threshold(self): self.reject(lambda: self.signature_call(lambda m, i, d: d.__setitem__("threshold", {"required": 3, "total": 4})))
    def test_fifth_23_signature_duplicate(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][1].__setitem__("signatureBase64", d["signers"][0]["signatureBase64"])))
    def test_fifth_24_signature_wrong_key(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][0].__setitem__("publicKeySha256", "0" * 64)))
    def test_fifth_25_signature_wrong_authority(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][0].__setitem__("authorityEvidenceId", "PARTNER-ACCEPTANCE")))
    def test_fifth_26_signature_extra(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"].append(copy.deepcopy(d["signers"][0]))))
    def test_fifth_27_signature_int_verified(self): self.reject(lambda: self.signature_call(lambda m, i, d: d["signers"][0].__setitem__("verified", 1)))

    def test_fifth_28_false_test_hidden(self):
        doc = {"schemaVersion": "1.0.0", "recordType": "candidate-test-results", "suiteType": "exact-commit", "candidateId": "sha256:" + "c" * 64, "sourceCommit": "a" * 40, "gitTree": "b" * 40, "scopeSha256": "d" * 64, "allPassed": True, "summary": {"total": 1, "passed": 1, "failed": 0, "combinedLogSha256": "1" * 64}, "tests": [{"testId": "false.test", "passed": False, "assertionCount": 1, "logSha256": "2" * 64}]}
        self.reject(lambda: self.artifact_probe("EXACT-COMMIT-TESTS", {"testResultsSha256": doc}))

    def test_fifth_29_arbitrary_test_json(self): self.reject(lambda: self.artifact_probe("PULL-PAYMENTS", {"testResultsSha256": {"garbage": True}}))

    def test_fifth_30_untrusted_liability_acceptors(self):
        doc = {"schemaVersion": "1.0.0", "recordType": "bounded-liability-decision", "candidateId": "sha256:" + "c" * 64, "subjectOrganizationId": "organization:" + "1" * 64, "partnerOrganizationId": "organization:" + "2" * 64, "currency": "USD", "perEventCap": "1", "aggregateCap": "1", "maximumLoss": "1", "reserveAmount": "1", "remedies": ["x"], "residualLiability": "x", "acceptedByIdentityIds": ["identity:" + "8" * 64, "identity:" + "9" * 64]}
        self.reject(lambda: self.artifact_probe("BOUNDED-LIABILITIES", {"capsArtifactSha256": doc}))

    def test_fifth_31_unresolved_unlinkability(self):
        doc = {"schemaVersion": "1.0.0", "recordType": "chain-specific-trust-portability", "candidateId": "sha256:" + "c" * 64, "subjectOrganizationId": "organization:" + "1" * 64, "partnerOrganizationId": "organization:" + "2" * 64, "chains": ["base"], "consentMode": "explicit", "portableCommitmentClasses": ["consent-receipt"], "unlinkabilityTestResultsSha256": "f" * 64, "crossChainValueFlow": False}
        self.reject(lambda: self.artifact_probe("PRIVACY-CROSS-CHAIN", {"privacyArtifactSha256": doc}))

    def test_fifth_32_reused_contact_channel(self):
        doc = {"schemaVersion": "1.0.0", "recordType": "controlled-contact-matrix", "candidateId": "sha256:" + "c" * 64, "subjectOrganizationId": "organization:" + "1" * 64, "requiredFunctions": ["change", "commercial", "incident", "privacy", "security", "support"], "channels": [{"function": f, "channelId": "same-channel", "controlTested": True} for f in ["change", "commercial", "incident", "privacy", "security", "support"]]}
        self.reject(lambda: self.artifact_probe("ACTUAL-CONTACTS", {"contactsArtifactSha256": doc}))

    def test_fifth_33_skeleton_commercial_agreement(self):
        doc = {"schemaVersion": "1.0.0", "agreementType": "executed-pilot-commercial-agreement", "candidateId": "sha256:" + "c" * 64, "subjectOrganizationId": "organization:" + "1" * 64, "partnerOrganizationId": "organization:" + "2" * 64, "subjectExecuted": True, "partnerExecuted": True, "signatureEvidenceId": "SIGNATURES"}
        self.reject(lambda: self.artifact_probe("COMMERCIAL-AGREEMENT", {"agreementSha256": doc}))

    def test_fifth_34_arbitrary_security_policy(self): self.reject(lambda: self.artifact_probe("SECURITY-POLICY-ACCEPTANCE", {"policySha256": {"garbage": True}}))
    def test_fifth_35_arbitrary_incident_plan(self): self.reject(lambda: self.artifact_probe("INCIDENT-PLAN-ACCEPTANCE", {"planSha256": {"garbage": True}}))
    def test_fifth_36_arbitrary_support_sla(self): self.reject(lambda: self.artifact_probe("SUPPORT-SLA-SELECTION", {"slaSha256": {"garbage": True}}))
    def test_fifth_37_arbitrary_change_raci(self): self.reject(lambda: self.artifact_probe("CHANGE-RACI-ACCEPTANCE", {"raciSha256": {"garbage": True}}))


if __name__ == "__main__":
    unittest.main()
