import bootstrap
from dataclasses import replace
import json
from pathlib import Path
import unittest
from features import Settings
from state import Episode
from contract import joint_state,person_state
from evaluate_integrated import group_states
from render_space import entries_for,page,sha,SOURCE,OUT


class ContractTests(unittest.TestCase):
    def test_frozen_configuration(self):
        self.assertEqual(sha(OUT/'candidates.json'),sha(SOURCE/'candidates.json'))
        self.assertEqual(json.loads((OUT/'candidates.json').read_text())['candidates'][0]['config']['hand_vertical'],.12)

    def test_score_boundaries(self):
        for score,expected in ((.099999,'LOW'),(.1,'HIGH'),(.05,'LOW')):
            self.assertEqual(joint_state([1,1,score],100,100),expected)
        for score,expected in ((.249999,'BELOW'),(.25,'HIGH')):
            self.assertEqual(person_state(dict(score=score,bbox=[1,1,20,20]),100,100),expected)

    def test_hold_refresh_and_cooldown(self):
        ep=Episode(replace(Settings(),display_hold=1.0))
        self.assertTrue(ep.step(0,True,False)['event'])
        self.assertTrue(ep.step(.5,False,True)['display'])
        self.assertFalse(ep.step(.8,True,False)['event'])
        self.assertTrue(ep.step(1.8,False,True)['display'])
        self.assertFalse(ep.step(1.8001,False,True)['display'])

    def test_reset_no_inherited_episode(self):
        a=Episode(replace(Settings(),display_hold=1.0));a.step(104.29494,True,False)
        b=Episode(replace(Settings(),display_hold=1.0));r=b.step(105.352669,False,False)
        self.assertFalse(r['display']);self.assertFalse(r['event']);self.assertFalse(r['latched'])

    def test_group_priority_and_uncertainty(self):
        view={'pairs':[{'orders':[0,1]}]};holds={}
        p=dict(token='S1',orders=[0,1],reset=False,hit=True)
        self.assertEqual(group_states(view,{'pairs':[p]},0,holds)[0]['state'],'RED_CURRENT')
        p['hit']=False
        self.assertEqual(group_states(view,{'pairs':[p]},1,holds)[0]['state'],'YELLOW_HOLD')
        self.assertEqual(group_states(view,{'pairs':[p]},1.01,holds)[0]['state'],'BLUE_CANDIDATE')
        self.assertEqual(group_states({'pairs':[]},{'pairs':[]},.5,{'S1':0}),[])

    def test_l01_mapping_and_gap(self):
        entries=entries_for('SELF_L01');self.assertEqual(len(entries),8306)
        self.assertEqual(sum(e['kind']=='observation' for e in entries),8305)
        self.assertEqual(entries[1043]['row']['source_frame'],2541)
        self.assertEqual(entries[1045]['row']['source_frame'],2543)
        self.assertEqual(entries[1045]['row']['sample'],0)
        self.assertEqual(entries[1045]['part_id'],'L01_P02')
        self.assertAlmostEqual(entries[1045]['seconds']-entries[1043]['seconds'],1.057729)
        self.assertFalse(entries[1045]['result']['episode']['display'])
        im,meta=page(entries[1044],'SELF_L01',1044,len(entries))
        self.assertEqual(meta['state'],'GAP');self.assertEqual(meta['group_states'],[])
        self.assertEqual(im.getpixel((20,150)),(16,24,32))

    def test_l01_observation_counter(self):
        from render_l01_corrected import corrected_page
        entries=entries_for('SELF_L01')
        for index,expected in ((1043,1044),(1044,None),(1045,1045),(8305,8305)):
            _,meta=corrected_page(entries[index],'SELF_L01',index,8306)
            self.assertEqual(meta['observation_ordinal'],expected)
            self.assertEqual(meta['observation_total'],8305)


if __name__=='__main__':
    suite=unittest.defaultTestLoader.loadTestsFromTestCase(ContractTests)
    result=unittest.TextTestRunner(verbosity=2).run(suite)
    (OUT/'download/validation/targeted_tests.json').write_text(json.dumps(dict(
        status='PASS' if result.wasSuccessful() else 'FAIL',tests=result.testsRun,failures=len(result.failures),errors=len(result.errors)),indent=2))
    if not result.wasSuccessful():raise SystemExit(1)
