"""
test_spec_v45_manifest_enforcement_subprocess.py - Subprocess-Based Manifest Enforcement Tests (v45Ω)

PROOF-GRADE tests that verify SHA-256 manifest verification detects file tampering.
Tests run in fresh Python subprocesses to prove load-time cryptographic enforcement.

v45.0 Track B Authority: Manifest verification ensures tamper-evident integrity.
Legacy mode (v44 fallback) is removed.

Test Strategy:
1. Create temporary tampered spec file (modify one value)
2. Set env var to point to tampered location OR tamper original file
3. Run subprocess that imports the module
4. Expect RuntimeError with "MANIFEST MISMATCH" or "TRACK B AUTHORITY FAILURE"

Windows-compatible: Uses subprocess.run() with sys.executable.
"""

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

import pytest


class TestConstitutionalFloorsManifestEnforcement:
    """Subprocess-based proof tests for constitutional floors manifest verification."""

    def test_default_import_verifies_manifest_successfully(self):
        """PROOF: Default import with unmodified v45 specs passes manifest verification."""
        code = """
from arifos_core.enforcement.metrics import _load_floors_spec_unified
spec = _load_floors_spec_unified()
print('MANIFEST_VERIFIED:SUCCESS')
"""
        result = subprocess.run(
            [sys.executable, "-c", code],
            capture_output=True,
            text=True,
            timeout=10,
            env={**os.environ} # v45 is strict by default
        )

        assert result.returncode == 0, f"Process should succeed with valid manifest, got stderr: {result.stderr}"
        assert "MANIFEST_VERIFIED:SUCCESS" in result.stdout, \
            f"Expected success marker, got stdout: {result.stdout}"

    def test_tampered_spec_file_triggers_manifest_mismatch(self):
        """PROOF: Tampering with spec file triggers manifest verification failure."""
        # Create temp copy of spec with modified value
        with tempfile.TemporaryDirectory() as tmpdir:
            tmp_spec_path = Path(tmpdir) / "tampered_floors.json"

            # Read original spec
            original_path = Path("spec/v45/constitutional_floors.json")
            with open(original_path, 'r', encoding='utf-8') as f:
                spec_data = json.load(f)

            # Tamper: change Truth threshold from 0.99 to 0.50 (should fail validation)
            spec_data['floors']['truth']['threshold'] = 0.50

            # Write tampered spec
            with open(tmp_spec_path, 'w', encoding='utf-8') as f:
                json.dump(spec_data, f, indent=2)

            # Try to load with tampered spec via env override
            # In v45, this fails PATH check (not manifest mismatch per se) because file is outside repo
            # effectively same result: Failure to load untrusted spec.
            code = """
from arifos_core.enforcement.metrics import _load_floors_spec_unified
print('SHOULD NOT REACH HERE')
"""
            result = subprocess.run(
                [sys.executable, "-c", code],
                capture_output=True,
                text=True,
                timeout=10,
                env={**os.environ, 'ARIFOS_FLOORS_SPEC': str(tmp_spec_path)}
            )

            # Should fail due to TRACK B AUTHORITY FAILURE (Path restriction or manifest mismatch if we bypassed path)
            assert result.returncode != 0, \
                f"Should have failed with tampered spec, got stdout: {result.stdout}"

            # Should contain authority error
            stderr_lower = result.stderr.lower()
            assert "track b authority failure" in stderr_lower, \
                f"Expected authority error, got stderr: {result.stderr}"

    def test_missing_manifest_triggers_hard_fail(self):
        """PROOF: Missing manifest file triggers RuntimeError in strict mode."""
        # Temporarily rename BOTH v45 and v44 manifests to simulate missing
        # If we only rename v45, metrics.py falls back to v44 (if present) and passes
        manifest_v45 = Path("spec/v45/MANIFEST.sha256.json")
        backup_v45 = Path("spec/v45/MANIFEST.sha256.json.bak")
        manifest_v44 = Path("spec/v44/MANIFEST.sha256.json")
        backup_v44 = Path("spec/v44/MANIFEST.sha256.json.bak")

        # Skip if already backed up
        if backup_v45.exists() or backup_v44.exists():
            pytest.skip("Manifest backup already exists, cleanup needed")

        renamed_v45 = False
        renamed_v44 = False

        try:
            if manifest_v45.exists():
                shutil.move(str(manifest_v45), str(backup_v45))
                renamed_v45 = True
            
            if manifest_v44.exists():
                shutil.move(str(manifest_v44), str(backup_v44))
                renamed_v44 = True

            code = """
from arifos_core.enforcement.metrics import _load_floors_spec_unified
print('SHOULD NOT REACH HERE')
"""
            result = subprocess.run(
                [sys.executable, "-c", code],
                capture_output=True,
                text=True,
                timeout=10,
                env={**os.environ}
            )

            # Should fail
            assert result.returncode != 0, \
                f"Should have failed with missing manifest, got stdout: {result.stdout}"

            # Should contain manifest error
            stderr_lower = result.stderr.lower()
            assert any(phrase in stderr_lower for phrase in [
                "manifest not found",
                "track b authority failure",
                "cryptographic manifest"
            ]), f"Expected manifest error, got stderr: {result.stderr}"

        finally:
            # Restore manifests
            if renamed_v45 and backup_v45.exists():
                shutil.move(str(backup_v45), str(manifest_v45))
            if renamed_v44 and backup_v44.exists():
                shutil.move(str(backup_v44), str(manifest_v44))


class TestManifestIntegrityProof:
    """Direct proof tests for manifest verification logic."""

    def test_compute_sha256_matches_manifest(self):
        """PROOF: Current spec files match manifest hashes (v45)."""
        from arifos_core.spec.manifest_verifier import compute_sha256, load_manifest
        from pathlib import Path

        manifest = load_manifest(Path("spec/v45/MANIFEST.sha256.json"))

        # Verify at least one file hash
        test_file = "spec/v45/constitutional_floors.json"
        
        # Manifest keys might be relative paths (normalized)
        # Check normalization
        key_found = None
        for k in manifest['files'].keys():
            if "constitutional_floors.json" in k and "v45" in k:
                key_found = k
                break
        
        if not key_found:
             # Fallback if specific file isn't in manifest (unlikely)
             pytest.fail(f"Could not find {test_file} in manifest keys: {list(manifest['files'].keys())}")

        expected_hash = manifest['files'][key_found]
        actual_hash = compute_sha256(Path(test_file))

        assert actual_hash == expected_hash, \
            f"Hash mismatch for {test_file}: expected {expected_hash}, got {actual_hash}"

    def test_manifest_contains_all_v45_specs(self):
        """PROOF: Manifest covers all v45 spec files."""
        from arifos_core.spec.manifest_verifier import load_manifest
        from pathlib import Path

        manifest = load_manifest(Path("spec/v45/MANIFEST.sha256.json"))

        required_files = [
            "spec/v45/constitutional_floors.json",
            "spec/v45/genius_law.json",
            "spec/v45/session_physics.json",
            "spec/v45/red_patterns.json",
            "spec/v45/schema/constitutional_floors.schema.json",
            "spec/v45/schema/genius_law.schema.json",
            "spec/v45/schema/session_physics.schema.json",
            "spec/v45/schema/red_patterns.schema.json",
        ]

        # Use normalized check
        manifest_keys = set(manifest['files'].keys())
        
        for file_path in required_files:
            # On Windows/Linux, paths might vary in separators
            # manifest keys are usually posix style.
            # We iterate and check
            found = False
            file_path_norm = str(Path(file_path)).replace("\\", "/")
            
            for k in manifest_keys:
                k_norm = k.replace("\\", "/")
                if file_path_norm in k_norm:
                    found = True
                    break
            
            assert found, f"Manifest missing required file: {file_path}"


if __name__ == "__main__":
    # Run tests with verbose output
    pytest.main([__file__, "-v", "--tb=short"])
