#!/usr/bin/env python3
"""
Property tests (Hypothesis) - cheap fuzz with big confidence.

Three quick property tests:
1. Changing prompt never changes raw_symbols (same seed)
2. Morph intersection never produces allowed_ids==[]
3. Macro quota respected for any positive integer
"""
import os
import sys

os.environ["VAULT_DISABLED"] = "1"

try:
    from hypothesis import given, strategies as st, settings
    HYPOTHESIS_AVAILABLE = True
except ImportError:
    HYPOTHESIS_AVAILABLE = False
    print("⚠ Hypothesis not installed, skipping property tests")
    print("  Install with: pip install hypothesis")

if HYPOTHESIS_AVAILABLE:
    from helix_cdc.cdc_decode import CDCDecoder

    @given(
        prompt1=st.text(min_size=0, max_size=100),
        prompt2=st.text(min_size=0, max_size=100),
        seed=st.integers(min_value=0, max_value=9999)
    )
    @settings(max_examples=20, deadline=5000)
    def test_prompt_invariance(prompt1, prompt2, seed):
        """Property: Changing prompt never changes raw_symbols (same seed)."""
        dec1 = CDCDecoder(seed_path=None)
        _, tr1 = dec1.generate_from_anchor(prompt1, max_new_tokens=32, temperature=0.2, seed=seed)

        dec2 = CDCDecoder(seed_path=None)
        _, tr2 = dec2.generate_from_anchor(prompt2, max_new_tokens=32, temperature=0.2, seed=seed)

        # Prompts don't affect vault-anchored generation
        assert tr1["raw_symbols"] == tr2["raw_symbols"], f"Seed {seed}: prompts affected output"

    @given(quota=st.integers(min_value=0, max_value=10))
    @settings(max_examples=10, deadline=5000)
    def test_macro_quota_respected(quota):
        """Property: Macro quota respected for any positive integer."""
        os.environ["GLYPHSCOPE_MACRO_QUOTA"] = str(quota)
        os.environ["GLYPHSCOPE_MACRO_MODE"] = "auto"  # Allow stochastic macro choice
        dec = CDCDecoder(seed_path=None)
        _, tr = dec.generate_from_anchor("", max_new_tokens=64, temperature=0.2, seed=999)

        # Macro uses never exceed quota
        assert tr["macro_uses"] <= quota, f"Quota {quota} violated: {tr['macro_uses']} uses"
        assert tr["macro_quota"] == quota, f"Quota {quota} not recorded"

        # Clean up
        os.environ.pop("GLYPHSCOPE_MACRO_QUOTA", None)
        os.environ.pop("GLYPHSCOPE_MACRO_MODE", None)

    def test_morph_intersection_nonempty():
        """Property: SAT allowed_ids never empty (morph intersection safe)."""
        # This is implicitly tested by generation not failing
        # If allowed_ids were empty, generation would crash
        dec = CDCDecoder(seed_path=None)
        _, tr = dec.generate_from_anchor("", max_new_tokens=64, temperature=0.2, seed=123)

        # Every step must have had non-empty allowed set
        for step in tr["tile_stream"]:
            assert step["sat_allowed"] > 0, f"Step {step['step']}: empty allowed_ids"

def main():
    if not HYPOTHESIS_AVAILABLE:
        print("✅ SKIP: Hypothesis not available (install with: pip install hypothesis)")
        return 0

    print("=== Property Tests (Hypothesis) ===")

    # Run property tests
    try:
        print("\n1. Prompt invariance (20 examples)")
        test_prompt_invariance()
        print("✅ PASS: prompts don't affect output")

        print("\n2. Macro quota respected (10 quotas)")
        test_macro_quota_respected()
        print("✅ PASS: quota never exceeded")

        print("\n3. Morph intersection non-empty")
        test_morph_intersection_nonempty()
        print("✅ PASS: allowed_ids never empty")

        print("\n✅ ALL PROPERTY TESTS PASSED")
        return 0
    except AssertionError as e:
        print(f"\n❌ FAIL: {e}")
        return 1

if __name__ == "__main__":
    sys.exit(main())
