"""Temporary hold bypass preserves admission, identity and episode ownership."""
import unittest

from app.fall_config import FallConfig
from app.fall_runtime import FallSettings, project_fall
from app.stream_settings import patch_settings
from tests.fall_fixtures import Sequence


class SkipHoldTests(unittest.TestCase):
    def test_default_off_and_strict_boolean(self):
        self.assertFalse(FallConfig().test_skip_down_hold)
        for value in (0, 1, None, 'true'):
            with self.subTest(value=value), self.assertRaises(ValueError):
                FallConfig(test_skip_down_hold=value)

    def test_on_confirms_current_candidate_frame_once(self):
        s = Sequence(test_skip_down_hold=True)
        s.arm()
        result = s.down(.7)
        self.assertEqual(result.decisions[0].state, 'FALL_CONFIRMED')
        self.assertEqual(len(result.event_candidates), 1)
        event = result.event_candidates[0]
        self.assertEqual(event.frame, result.frame)
        self.assertEqual(event.track_id, 1)
        self.assertEqual(event.object_ref, '0')
        self.assertEqual(event.bbox_xyxy, (.1, .1, .9, .9))
        for i in range(8, 25):
            self.assertFalse(s.down(i / 10).event_candidates)

    def test_off_keeps_original_hold(self):
        s = Sequence()
        s.arm()
        for i in range(7, 14):
            self.assertFalse(s.down(i / 10).event_candidates)
        self.assertEqual(len(s.down(1.4).event_candidates), 1)

    def test_initial_down_does_not_confirm(self):
        s = Sequence(test_skip_down_hold=True)
        for i in range(20):
            self.assertFalse(s.down(i / 10).event_candidates)

    def test_low_score_missing_or_untracked_does_not_confirm(self):
        for options in ({'score': .1}, {'missing': tuple(range(17))}, {'track_id': None}):
            with self.subTest(options=options):
                s = Sequence(test_skip_down_hold=True)
                s.arm()
                self.assertFalse(s.down(.7, **options).event_candidates)

    def test_old_transition_does_not_confirm(self):
        s = Sequence(test_skip_down_hold=True)
        s.arm()
        self.assertFalse(s.step(.7, angle=40., hip=(500., 520.), span=200.).event_candidates)
        self.assertFalse(s.down(1.6).event_candidates)

    def test_recovery_is_still_required_for_next_episode(self):
        s = Sequence(test_skip_down_hold=True)
        s.arm()
        self.assertEqual(len(s.down(.7).event_candidates), 1)
        for i in range(8, 22):
            self.assertFalse(s.step(i / 10).event_candidates)
        self.assertEqual(len(s.down(2.2).event_candidates), 1)

    def test_run_reset_requires_new_arming(self):
        s = Sequence(test_skip_down_hold=True)
        s.arm()
        self.assertTrue(s.down(.7).event_candidates)
        with self.assertRaises(ValueError):
            s.down(.8, frame_changes={'run_id': 'run-b', 'run_generation': 2})
        s.engine.clear()
        self.assertFalse(s.down(.8, frame_changes={'run_id': 'run-b', 'run_generation': 2}).event_candidates)

    def test_settings_patch_and_web_projection_preserve_internal_option(self):
        spec = {'stream_id': 'test', 'server_id': 'test', 'source_id': '77',
                'store': False, 'active_learning': False,
                # Explicit Off makes this a real patch, independent of site default.
                'fall': {'enabled': True, 'person_score': .3, 'test_skip_down_hold': False}}
        candidate = patch_settings(spec, {'fall': {'test_skip_down_hold': True}})
        expected = FallSettings.parse(spec['fall']).settings_payload()
        expected['test_skip_down_hold'] = True
        self.assertEqual(candidate['fall'], expected)
        project_fall(candidate, [{'scenarioCode': 'FALL', 'enabled': True,
                                 'confidence': .3, 'rois': []}])
        self.assertTrue(candidate['fall']['test_skip_down_hold'])
        self.assertEqual(candidate['fall']['person_score'], .3)
        self.assertNotIn('test_skip_down_hold', FallSettings.parse(candidate['fall']).payload())


if __name__ == '__main__':
    unittest.main()
