import copy,hashlib,json,pathlib,subprocess,unittest
ROOT=pathlib.Path(__file__).resolve().parents[1]
PHP=ROOT/'receiver/ym-sync-catalog-v2-atomic-review.php'
VESSEL='4b4fba28-49b7-4d18-87fb-63a1a5c1930e'

def fixture(name):
    return json.loads((ROOT/'fixtures'/f'atomic-{name}.json').read_text())
def check(obj, vessel=VESSEL):
    return subprocess.run(['php',str(PHP),'--validate-only',vessel],input=json.dumps(obj),text=True,capture_output=True)
class Contract(unittest.TestCase):
    def test_fixture_envelopes_valid(self):
        for n in ('header','chunk-000','chunk-001','finalize'):
            with self.subTest(n=n):
                r=check(fixture(n));self.assertEqual(r.returncode,0,r.stderr)
                self.assertEqual(json.loads(r.stdout)['status'],'validated')
    def test_modified_payload_rejected(self):
        x=fixture('header');x['payload_json']+=' ';self.assertNotEqual(check(x).returncode,0)
    def test_identity_rejected(self):
        self.assertNotEqual(check(fixture('header'),'17b19e77-0746-4cd6-9e37-31b2564ebddb').returncode,0)
    def test_changed_sequence_key_rejected(self):
        x=fixture('chunk-000');x['entity_key']='bad';self.assertNotEqual(check(x).returncode,0)
    def test_finalize_revision_unverifiable_offline(self):
        # Bridge cannot validate finalize revision without database. Verified in receiveAtomic.
        x=fixture('finalize');x['entity_revision']=2
        self.assertEqual(check(x).returncode,0)
    def test_fixtures_hashes(self):
        for n in ('header','chunk-000','chunk-001','finalize'):
            x=fixture(n)
            self.assertEqual(hashlib.sha256(x['payload_json'].encode()).hexdigest(),x['content_hash'])
    def test_receipt_table_has_identical_layout(self):
        sql=(ROOT/'sql/receipts_test_only.sql').read_text()
        self.assertIn('PRIMARY KEY (vessel_id,outbox_seq_no)',sql)
        self.assertIn('content_hash BINARY(32) NOT NULL',sql)
    def test_atomic_design_invariants(self):
        src=PHP.read_text()
        self.assertIn('$db->beginTransaction();',src)
        self.assertIn('INSERT INTO sync_receipts',src)
        self.assertIn("$result['status']==='incomplete'",src)
        self.assertIn("'finalize manifest/revision mismatch'",src)
        self.assertIn("INJECTED_ATOMIC_LOST_ACK_AFTER_COMMIT",src)
if __name__=='__main__':unittest.main()
