from __future__ import annotations

import copy
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 checker
from strict_json_schema import StrictJSONError, load_json, loads_json, validate


class ExpandedPostRemediationRegressions(unittest.TestCase):
    """Named reproductions of all 19 expanded independent-audit attacks."""

    @classmethod
    def setUpClass(cls) -> None:
        cls.base = load_json(ROOT / "manifests/organizational-readiness.template.json")

    def rejected(self, item: dict) -> None:
        with tempfile.TemporaryDirectory() as td:
            path = Path(td) / "manifest.json"
            path.write_text(json.dumps(item), encoding="utf-8")
            with self.assertRaises((checker.Blocked, StrictJSONError)):
                checker.run(path)

    def ready_scope(self, module: str, route: str, action: str) -> dict:
        item = copy.deepcopy(self.base)
        item["partnerReady"] = True
        item["status"] = "candidate-bound-ready"
        modules = sorted(
            row["module"]
            for row in load_json(checker.REPOSITORY / "v3-migration/completeness/supported-v3-modules.json")["modules"]
        )
        item["scope"].update(
            selectedModule=module,
            selectedRouteId=route,
            selectedActionClass=action,
            deniedModules=[value for value in modules if value != module],
        )
        return item

    def test_expanded_01_boolean_type_confusion_commitment(self):
        item = copy.deepcopy(self.base)
        item["commitments"]["flourishingCannotSlashPrincipal"] = 1
        self.rejected(item)

    def test_expanded_02_boolean_type_confusion_active_limit(self):
        item = copy.deepcopy(self.base)
        item["scope"]["activePartnerLimit"] = True
        self.rejected(item)

    def test_expanded_03_boolean_type_confusion_production_claim(self):
        item = copy.deepcopy(self.base)
        item["scope"]["productionClaim"] = 0
        self.rejected(item)

    def test_expanded_04_opaque_hashed_input_allowed_prefix(self):
        item = copy.deepcopy(self.base)
        item["decisionInputPolicy"]["observedInputClasses"] = ["allowed:sha256-deadbeef"]
        self.rejected(item)

    def test_expanded_05_monolithic_global_v3_synonym(self):
        module = load_json(checker.REPOSITORY / "v3-migration/completeness/supported-v3-modules.json")["modules"][0]["module"]
        with self.assertRaises(checker.Blocked):
            checker.validate_scope(self.ready_scope(module, "global-v3", "write-activation"))

    def test_expanded_06_nonexistent_route_not_canonicalized(self):
        module = load_json(checker.REPOSITORY / "v3-migration/completeness/supported-v3-modules.json")["modules"][0]["module"]
        with self.assertRaises(checker.Blocked):
            checker.validate_scope(self.ready_scope(module, "base.nonexistent.write-activation", "write-activation"))

    def test_expanded_07_prohibited_reputation_module_selectable(self):
        with self.assertRaises(checker.Blocked):
            checker.validate_scope(self.ready_scope("StreetCredV3", "base.streetcred.write-activation", "write-activation"))

    def test_expanded_08_module_action_semantic_mismatch(self):
        with self.assertRaises(checker.Blocked):
            checker.validate_scope(self.ready_scope("StreetCredV3", "base.streetcred.signer-guardian", "signer-guardian"))

    def test_expanded_09_self_declared_trust_and_unrelated_authority(self):
        with self.assertRaises(checker.Blocked):
            checker.load_trust(datetime.now(timezone.utc))

    def test_expanded_10_partner_internal_identity_overlap(self):
        identity = "identity:" + "1" * 64
        ids = {
            identity: {
                "identityClass": "internal-human",
                "organizationId": "organization:" + "a" * 64,
                "allowedRoles": ["security-contact", "partner-authorized-signatory"],
                "_public": b"\x01" * 32,
            }
        }
        manifest = copy.deepcopy(self.base)
        manifest["subjectOrganizationId"] = "organization:" + "a" * 64
        manifest["partnerOrganizationId"] = "organization:" + "b" * 64
        manifest["roles"][0]["identityId"] = identity
        manifest["roles"][0]["contactChannelId"] = "channel:" + "2" * 64
        manifest["attestation"] = {
            "preparedByIdentityId": identity,
            "reviewedByIdentityId": identity,
            "authorizedByIdentityId": identity,
            "partnerAcceptedByIdentityId": identity,
            "signedAtUtc": "2026-08-25T00:00:00Z",
            "signatureEvidenceId": "SIGNATURES",
        }
        original = checker.load_trust
        checker.load_trust = lambda _as_of=None: (ids, {
            "subjectOrganizationId": manifest["subjectOrganizationId"],
            "partnerOrganizationId": manifest["partnerOrganizationId"],
        })
        try:
            with self.assertRaises(checker.Blocked):
                checker.validate_evidence(
                    manifest, "sha256:" + "3" * 64, "sha256:" + "4" * 64,
                    datetime(2026, 8, 25, tzinfo=timezone.utc),
                )
        finally:
            checker.load_trust = original

    def test_expanded_11_critical_claims_require_objective_artifact_refs(self):
        critical = {"EXACT-COMMIT-TESTS", "FAIL-CLOSED-APIS", "IMMUTABLE-EVIDENCE", "MODULE-ROLLOUT", "COMMERCIAL-AGREEMENT", "SIGNATURES"}
        self.assertTrue(all(checker.EVIDENCE_SPECS[key]["prefixes"] for key in critical))
        paths = [checker.EVIDENCE_SPECS[key]["prefixes"][0] for key in sorted(checker.EVIDENCE_SPECS)]
        self.assertEqual(len(paths), len(set(paths)))

    def test_expanded_12_cross_evidence_organization_consistency(self):
        source = (ROOT / "scripts/check_organizational_readiness.py").read_text(encoding="utf-8")
        self.assertIn('claim["subjectOrganizationId"] == manifest["subjectOrganizationId"]', source)
        self.assertIn('claim["partnerOrganizationId"] == manifest["partnerOrganizationId"]', source)

    def test_expanded_13_operations_status_candidate_semantic_binding(self):
        source = (ROOT / "scripts/check_organizational_readiness.py").read_text(encoding="utf-8")
        self.assertIn('"operationsStatus": "v3-operations/release-status.json"', source)
        self.assertIn('operations.get("scopeSha256") == scope_hash', source)
        self.assertIn('"canonicalArtifactDigests": source_bindings', source)

    def test_expanded_14_canonical_artifacts_selected_scope_binding(self):
        source = (ROOT / "scripts/check_organizational_readiness.py").read_text(encoding="utf-8")
        for token in ('"selectedModule"', '"selectedRouteId"', '"selectedActionClass"', '"scopeSha256"'):
            self.assertIn(token, source)

    def test_expanded_15_rfc8032_valid_empty(self):
        public = bytes.fromhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a")
        signature = bytes.fromhex(
            "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155"
            "5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
        )
        self.assertTrue(checker.verify_ed25519(public, signature, b""))

    def test_expanded_16_rfc8032_tampered_message(self):
        public = bytes.fromhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a")
        signature = bytes.fromhex(
            "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155"
            "5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
        )
        self.assertFalse(checker.verify_ed25519(public, signature, b"x"))

    def test_expanded_17_ed25519_all_zero_material(self):
        self.assertFalse(checker.verify_ed25519(bytes(32), bytes(64), b""))

    def test_expanded_18_strict_duplicate_key(self):
        with self.assertRaises(StrictJSONError):
            loads_json('{"a":1,"a":2}')

    def test_expanded_19_strict_nan(self):
        with self.assertRaises(StrictJSONError):
            loads_json('{"a":NaN}')

    def test_new_exact_own_keys_rejects_extra_field(self):
        item = copy.deepcopy(self.base)
        item["scope"]["unexpected"] = False
        self.rejected(item)

    def test_new_tracked_blob_exactness_detects_worktree_substitute(self):
        commit = checker.git("rev-parse", "HEAD")
        tracked = "pr10/partner-package/README.md"
        with tempfile.TemporaryDirectory() as td:
            substitute = Path(td) / "README.md"
            substitute.write_bytes(checker.git_bytes(commit, tracked) + b"\ntampered\n")
            with self.assertRaises(checker.Blocked):
                checker.require_tracked_exact(tracked, substitute, commit, "fixture")

    def test_new_identity_schema_requires_key_ownership_proof(self):
        trust = {
            "$schema": "../schemas/trusted-identities.schema.json",
            "schemaVersion": "2.0.0",
            "status": "external-human-attested",
            "subjectOrganizationId": "organization:" + "a" * 64,
            "partnerOrganizationId": "organization:" + "b" * 64,
            "externalTrustRootId": "trust-root:" + "c" * 64,
            "registryAttestation": {
                "registryPayloadSha256": "d" * 64,
                "issuedAtUtc": "2026-08-25T00:00:00Z",
                "expiresAtUtc": "2026-09-25T00:00:00Z",
                "signatureBase64": "A" * 86 + "==",
            },
            "identities": [{
                "identityId": "identity:" + "e" * 64,
                "identityClass": "internal-human",
                "organizationId": "organization:" + "a" * 64,
                "allowedRoles": ["release-manager"],
                "publicKeyBase64": "A" * 43 + "=",
                "membershipEvidenceId": "ROLE-ASSIGNMENTS",
                "keyOwnershipEvidenceId": "SIGNATURES",
                "authorityEvidenceId": "CANDIDATE-BINDING",
                "externalAttestationId": "external-attestation:" + "f" * 64,
            }],
        }
        with self.assertRaises(StrictJSONError):
            validate(trust, load_json(ROOT / "schemas/trusted-identities.schema.json"))


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