import copy
import json
import unittest
from pathlib import Path

HERE = Path(__file__).resolve().parents[1]
import sys
sys.path.insert(0, str(HERE))
from pack_rules import validate_pack

RULES = json.loads((HERE / "CHECK_RULES.json").read_text(encoding="utf-8"))

def sentinel_pack():
    return {
        "asset_id":"TEST-SENTINEL-NOT-ANSWER-KEY",
        "target":{"target_ref":"target://test","material_model":"GLTF2_METALLIC_ROUGHNESS","target_constraint_scope":"STANDARD_DECLARED_TARGET","uv_overlap_policy":"UNIQUE_REQUIRED","channel_packing":"SEPARATE_TEXTURES","packing_convention":None},
        "uv":{"uv_set":"UV_TEST","overlap_observation":"NO_UNINTENDED_OVERLAP_OBSERVED","stretch_check":{"metric":"TEST","threshold":{"operator":"<=","value":1.1},"observed":1.0,"unit":"ratio"},"texel_density_policy":{"metric":"TEST_DENSITY","target_band":{"min":10,"max":20,"unit":"test"},"observed_range":{"min":12,"max":18,"unit":"test"}}},
        "texture_resolution":"TEST",
        "channels":[
            {"role":"base_color","map_ref":"test://base","transfer":"SRGB","uv_set":"UV_TEST","packed_channel":None},
            {"role":"metallic","map_ref":"test://metal","transfer":"LINEAR","uv_set":"UV_TEST","packed_channel":None},
            {"role":"roughness","map_ref":"test://rough","transfer":"LINEAR","uv_set":"UV_TEST","packed_channel":None},
            {"role":"normal","map_ref":"test://normal","transfer":"LINEAR","uv_set":"UV_TEST","packed_channel":None},
            {"role":"occlusion","map_ref":"test://ao","transfer":"LINEAR","uv_set":"UV_TEST","packed_channel":None}],
        "normal_convention":"TANGENT_SPACE_POSITIVE_Y_TEST",
        "ai_provenance":{"ai_assist_method":"TEST","source_asset_ref":"test://source","rights_provenance_ref":"test://rights","rights_status":"VERIFIED_RIGHTS_CLEARED","manual_edit_log":["TEST edit"]},
        "lighting_validation":[{"lighting_id":"L1","observation":"TEST visible observation one long enough for a human reviewer."},{"lighting_id":"L2","observation":"TEST visible observation two long enough for a human reviewer."}],
        "interoperability_evidence":{"target_ref":"target://test","binding_checks":[{"check":"uv","expected":"UV_TEST","observed":"UV_TEST"},{"check":"channels","expected":"separate","observed":"separate"}],"export_observation":"TEST only","export_only_proof":False},
        "decision":{"final_state":"ACCEPT","rationale":"TEST-SENTINEL only","triggers":[{"type":"review","condition":"TEST-SENTINEL future condition"}]}
    }

class TestRules(unittest.TestCase):
    def test_consistent_test_sentinel(self): self.assertEqual(validate_pack(sentinel_pack(), RULES), [])
    def test_one_applicable_trigger_is_sufficient(self):
        p=sentinel_pack(); p["decision"]["triggers"]=[{"type":"pivot","condition":"TEST condition"}]
        self.assertFalse(any(e["code"]=="E_DECISION_TRIGGERS" for e in validate_pack(p,RULES)))
    def test_missing_trigger_is_reported(self):
        p=sentinel_pack(); p["decision"]["triggers"]=[]
        self.assertTrue(any(e["code"]=="E_DECISION_TRIGGERS" for e in validate_pack(p,RULES)))
    def test_roughness_srgb_is_reported(self):
        p=sentinel_pack(); next(c for c in p["channels"] if c["role"]=="roughness")["transfer"]="SRGB"
        self.assertTrue(any(e["code"]=="E_ROUGHNESS_TRANSFER" for e in validate_pack(p,RULES)))
    def test_unique_required_rejects_mirror_for_positive(self):
        p=sentinel_pack(); p["uv"]["overlap_observation"]="INTENTIONAL_MIRRORED"
        self.assertTrue(any(e["code"]=="E_OVERLAP_POLICY" for e in validate_pack(p,RULES)))
    def test_unknown_provenance_blocks_positive(self):
        p=sentinel_pack(); p["ai_provenance"]["rights_status"]="UNKNOWN"
        self.assertTrue(any(e["code"]=="E_PROVENANCE_POSITIVE" for e in validate_pack(p,RULES)))
    def test_explicit_stretch_measurement_required_for_positive(self):
        p=sentinel_pack(); p["uv"]["stretch_check"]="PASS"
        self.assertTrue(any(e["code"]=="E_MISSING_CRITICAL_POSITIVE" for e in validate_pack(p,RULES)))
    def test_explicit_binding_records_required_for_positive(self):
        p=sentinel_pack(); p["interoperability_evidence"]["binding_checks"]=[]
        self.assertTrue(any(e["code"]=="E_INTEROP_EVIDENCE" for e in validate_pack(p,RULES)))
    def test_visible_rules_are_consumed(self):
        bad=copy.deepcopy(RULES); bad["rules"]=[r for r in bad["rules"] if r["id"]!="R10"]
        self.assertTrue(any(e["code"]=="E_VISIBLE_RULESET_INCOMPLETE" for e in validate_pack(sentinel_pack(),bad)))
    def test_shipped_fixtures_are_end_to_end_consumable_without_answer_key(self):
        for rel in [Path("fixtures/baseline_fixture.json"),Path("transfer/transfer_fixture.json")]:
            d=json.loads((HERE/rel).read_text(encoding="utf-8"))
            d["decision"]={"final_state":"HOLD","rationale":"TEST-SENTINEL-NOT-ANSWER-KEY; integration checks parser/rule compatibility only.","triggers":[{"type":"review","condition":"TEST-SENTINEL"}]}
            errors=validate_pack(d,RULES)
            structural={"E_VISIBLE_RULESET_INCOMPLETE","E_ROUGHNESS_TRANSFER","E_CHANNEL_TRANSFER","E_ORM_CONVENTION","E_ORM_BINDING","E_DECISION_TRIGGER_TYPE","E_DECISION_TRIGGER_CONDITION","E_DECISION_TRIGGERS"}
            self.assertFalse(any(e["code"] in structural for e in errors), (rel, errors))

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