from __future__ import annotations

import copy
import hashlib
import json
import sys
import tempfile
import unittest
from datetime import datetime, timezone
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 ThirdGenerationAuditRegressions(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 test_third_01_duplicate_key(self): self.reject(lambda: s.loads_json('{"x":1,"x":2}'))
    def test_third_02_nan(self): self.reject(lambda: s.loads_json('{"x":NaN}'))
    def test_third_03_infinity(self): self.reject(lambda: s.loads_json('{"x":Infinity}'))
    def test_third_04_const_true_vs_one(self): self.reject(lambda: s.validate(1, {"$schema":"https://json-schema.org/draft/2020-12/schema","const":True}))
    def test_third_05_enum_false_vs_zero(self): self.reject(lambda: s.validate(0, {"$schema":"https://json-schema.org/draft/2020-12/schema","enum":[False]}))

    def _input_reject(self, value):
        item = copy.deepcopy(self.base)
        item["decisionInputPolicy"]["observedInputClasses"] = [value]
        with tempfile.TemporaryDirectory() as td:
            path = Path(td) / "m.json"; path.write_text(json.dumps(item))
            self.reject(lambda: c.run(path))

    def test_third_06_protected_sha256(self): self._input_reject("allowed:sha256-deadbeef")
    def test_third_07_protected_derived(self): self._input_reject("allowed:derived-objective-onchain-fact")
    def test_third_08_protected_biometric(self): self._input_reject("allowed:biometric")
    def test_third_09_protected_opaque(self): self._input_reject("allowed:opaque")

    def _scope_reject(self, route, action):
        old_repo, old_registry = c.REPOSITORY, c.SCOPE_REGISTRY_PATH
        with tempfile.TemporaryDirectory() as td:
            root = Path(td)
            open_spec = root / c.CANONICAL_PATHS["openSpec"]; open_spec.parent.mkdir(parents=True)
            module = {"module":"SafeModule","path":"v3-src/contracts/safe_module.sol","capabilityTags":["objective-protocol"],"supportStatus":"supported-source","deploymentStatus":"deployed-authorized","allowedProtectedDecisions":[]}
            open_spec.write_text(json.dumps({"modules":[module],"unsafeCapabilityClasses":["reputation"]}))
            registry = root / "registry.json"
            registry.write_text(json.dumps({"$schema":"../schemas/pilot-scope-registry.schema.json","schemaVersion":"1.0.0","status":"candidate-bound-approved","network":"base","chainId":8453,"routes":[{"routeId":route,"module":"SafeModule","actionClass":action,"moduleSourcePath":module["path"],"requiredDeploymentContract":"InventedContract","rolloutStageId":"stage:"+"a"*64,"protectedDecisionInfluence":[],"status":"candidate-bound-approved"}]}))
            c.REPOSITORY, c.SCOPE_REGISTRY_PATH = root, registry
            try:
                item = copy.deepcopy(self.base)
                item["scope"].update(selectedModule="SafeModule", selectedActionClass=action, selectedRouteId=route, deniedModules=[])
                self.reject(lambda: c.validate_scope(item))
            finally:
                c.REPOSITORY, c.SCOPE_REGISTRY_PATH = old_repo, old_registry

    def test_third_10_registered_monolithic_route(self): self._scope_reject("base.everything-v3.write-activation","write-activation")
    def test_third_11_registered_nonexistent_route(self): self._scope_reject("base.nonexistent.write-activation","write-activation")
    def test_third_12_registry_incompatible_action(self): self._scope_reject("base.safe.signer-guardian","signer-guardian")

    def _artifact_reject(self, evidence_id, facts):
        item = copy.deepcopy(self.base)
        item["subjectOrganizationId"]="organization:"+"1"*64
        item["partnerOrganizationId"]="organization:"+"2"*64
        item["releaseCandidate"]["sourceCommit"]="a"*40
        item["releaseCandidate"]["evidenceCommit"]="b"*40
        artifact={"schemaVersion":"1.0.0","artifactId":evidence_id,"artifactType":c.EVIDENCE_SPECS[evidence_id]["claim"],"candidateId":"sha256:"+"c"*64,"sourceCommit":"a"*40,"subjectOrganizationId":item["subjectOrganizationId"],"partnerOrganizationId":item["partnerOrganizationId"],"scopeSha256":"d"*64,"facts":facts}
        self.reject(lambda: c.validate_objective_artifact(artifact,evidence_id,item,"sha256:"+"c"*64,"d"*64))

    def test_third_13_arbitrary_test_digest(self): self._artifact_reject("EXACT-COMMIT-TESTS",{"testSourceCommit":"a"*40,"testGitTree":"b"*40,"testResultsSha256":"0"*64,"allPassed":True})
    def test_third_14_arbitrary_operations_digest(self): self._artifact_reject("CANONICAL-OPERATIONS",{"operationsSha256":"0"*64,"candidateIdMatched":True,"allStagesComplete":True})
    def test_third_15_arbitrary_agreement_digest(self): self._artifact_reject("COMMERCIAL-AGREEMENT",{"agreementSha256":"0"*64,"subjectExecuted":True,"partnerExecuted":True})
    def test_third_16_arbitrary_contact_coverage(self): self._artifact_reject("ACTUAL-CONTACTS",{"channelsControlTested":True,"requiredFunctionsCovered":"anything"})

    def test_third_17_attestors_equal_assigned_role_holders(self):
        identities, roles = {}, []
        for index, role in enumerate(sorted(c.ROLE_IDS)):
            iid="identity:"+f"{index+1:064x}"
            identities[iid]={"allowedRoles":[role],"identityClass":"internal-human","_public":b"1"*32}
            roles.append({"roleId":role,"identityId":iid,"contactChannelId":"channel:"+hashlib.sha256(role.encode()).hexdigest(),"evidenceId":c.ROLE_EVIDENCE[role]})
        extras={}
        for index, role in enumerate(["release-manager","independent-reviewer","authorized-signatory"],50):
            iid="identity:"+f"{index:064x}";extras[role]=iid
            identities[iid]={"allowedRoles":[role],"identityClass":"internal-human","_public":b"2"*32}
        partner="identity:"+"f"*64
        identities[partner]={"allowedRoles":["partner-authorized-signatory"],"identityClass":"partner-human","_public":b"3"*32}
        item=copy.deepcopy(self.base);item["roles"]=roles
        item["attestation"]={"preparedByIdentityId":extras["release-manager"],"reviewedByIdentityId":extras["independent-reviewer"],"authorizedByIdentityId":extras["authorized-signatory"],"partnerAcceptedByIdentityId":partner,"signedAtUtc":"2026-08-25T00:00:00Z","signatureEvidenceId":"SIGNATURES"}
        old=c.load_trust;c.load_trust=lambda _=None:(identities,{"subjectOrganizationId":None,"partnerOrganizationId":None})
        try:
            with self.assertRaisesRegex(c.Blocked, "assigned to exact manifest role"):
                c.validate_evidence(item,"sha256:"+"a"*64,"b"*64,datetime(2026,8,25,tzinfo=timezone.utc))
        finally: c.load_trust=old

    def test_third_18_external_root_not_caller_selectable(self):
        source=(ROOT/"scripts/check_organizational_readiness.py").read_text()
        self.assertNotIn('VAULTFIRE_EXTERNAL_TRUST_ROOT_SHA256',source)
        self.assertIn("EXPECTED_EXTERNAL_TRUST_POLICY",source)
        self.reject(lambda: c.load_trust(datetime.now(timezone.utc)))

    def test_third_19_scope_registry_source_and_scope_binding(self):
        schema=s.load_json(ROOT/"schemas/pilot-scope-registry.schema.json")
        self.assertIn("sourceCommit",schema["properties"])
        self.assertIn("openSpecSha256",schema["properties"])
        self.assertIn("generatedRouteInventorySha256",schema["properties"])

    def test_third_20_rollout_stage_cross_binding(self):
        source=(ROOT/"scripts/check_organizational_readiness.py").read_text()
        self.assertIn('stage["stageId"] == route_row["rolloutStageId"]',source)

    def test_third_21_deployment_contract_cross_binding(self):
        source=(ROOT/"scripts/check_organizational_readiness.py").read_text()
        self.assertIn('stage["requiredDeploymentContract"] == route_row["requiredDeploymentContract"]',source)

    def test_third_22_candidate_digest_is_acyclic_and_deterministic(self):
        item=copy.deepcopy(self.base); item["subjectOrganizationId"]="organization:"+"1"*64;item["partnerOrganizationId"]="organization:"+"2"*64
        rc=item["releaseCandidate"];rc.update(packageVersion="freeze-v1",sourceCommit="a"*40,gitTree="b"*40,evidenceCommit="c"*40,evidenceTree="d"*40,deploymentManifest={"path":c.DEPLOYMENT_PATH,"sha256":"e"*64},canonicalBindings={key:{"path":path,"sha256":hashlib.sha256(key.encode()).hexdigest()} for key,path in c.CANONICAL_PATHS.items()})
        first=c.expected_candidate_id(item);second=c.expected_candidate_id(item)
        self.assertEqual(first,second)
        rc["sourceCommit"]="f"*40;rc["evidenceCommit"]="0"*40
        self.assertEqual(first,c.expected_candidate_id(item))

    def test_third_23_envelope_changes_without_source_blob_cycle(self):
        item=copy.deepcopy(self.base);rc=item["releaseCandidate"];rc.update(candidateId="sha256:"+"1"*64,sourceCommit="a"*40,gitTree="b"*40,evidenceCommit="c"*40,evidenceTree="d"*40)
        first=c.expected_envelope_id(item);rc["evidenceTree"]="e"*40
        self.assertNotEqual(first,c.expected_envelope_id(item))
        source=(ROOT/"scripts/check_organizational_readiness.py").read_text()
        self.assertNotIn('deployment.get("sourceCommit") == commit',source)

    def test_third_24_rfc8032_valid(self):
        pub=bytes.fromhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a")
        sig=bytes.fromhex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b")
        self.assertTrue(c.verify_ed25519(pub,sig,b""))

    def test_third_25_rfc8032_tamper(self):
        pub=bytes.fromhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a")
        sig=bytes.fromhex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b")
        self.assertFalse(c.verify_ed25519(pub,sig,b"x"))

    def test_additional_ready_is_structurally_blocked_without_pinned_authority(self):
        policy=s.load_json(ROOT/"config/trust-root-policy.json")
        self.assertEqual(policy["status"], "no-external-authority-established")
        self.assertIsNone(policy["expectedRootPath"])
        self.assertIsNone(policy["expectedRootId"])
        self.assertIsNone(policy["expectedRootSha256"])
        self.reject(lambda: c.load_trust(datetime.now(timezone.utc)))

    def test_additional_scope_upstream_must_antecede_candidate(self):
        source=(ROOT/"scripts/check_organizational_readiness.py").read_text()
        self.assertIn(
            '"merge-base", "--is-ancestor", registry_source_commit, commit',
            source,
        )

    def test_additional_resolved_objective_blobs_globally_unique(self):
        source=(ROOT/"scripts/check_organizational_readiness.py").read_text()
        self.assertIn("resolved typed evidence blob paths must be globally unique", source)
        self.assertIn("resolved typed evidence blob digests must be globally unique", source)


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