import os
import unittest
from types import SimpleNamespace
from unittest.mock import patch

from app.frame_ingress import FrameIngress
from app.input_timing import source_latest_enabled
from app.contracts.frames import FrameEnvelope, DetectionObservation
from app.intrusion import Intrusion, IntrusionConfig
from app.loitering import Loitering, LoiteringConfig


class SourceQueueTest(unittest.TestCase):
    def test_option_default_and_invalid(self):
        with patch.dict(os.environ, {}, clear=True): self.assertFalse(source_latest_enabled())
        with patch.dict(os.environ, CONTEXTRA_SOURCE_LATEST_TRIAL='1'):
            with self.assertRaises(ValueError): source_latest_enabled()

    def test_raw_skip_is_not_analysis_gap_until_target_slot_is_missing(self):
        values = {}
        def append(batch, frame, value): values[frame.frame_number] = value
        with patch.dict(os.environ, CONTEXTRA_SOURCE_LATEST_TRIAL='true'), \
                patch('app.frame_ingress.identity_meta_type', return_value=1), \
                patch('app.frame_ingress.rtp_meta_type', return_value=2), \
                patch('app.input_timing.gap_meta_type', return_value=3), \
                patch('app.frame_ingress.append_identity', side_effect=append):
            ingress = FrameIngress('s','3','stream','run',1)
            ingress.track_result_gaps = True
            for seq, (pts, raw_gap, selected, expected_gap) in enumerate(
                    [(0, False, True, False), (50_000_000, True, False, None),
                     (100_000_000, False, True, False), (400_000_000, True, True, True)]):
                f = SimpleNamespace(frame_number=seq, buffer_pts=pts, source_width=1280, source_height=720,
                    user_meta_items=lambda kind, gap=raw_gap: iter([object()] if kind==3 and gap else []))
                self.assertEqual(ingress.stamp(SimpleNamespace(frame_items=[f])), selected)
                if selected:
                    data=values[seq]['frame']; frame=SimpleNamespace(**data)
                    self.assertEqual(ingress.result_gap(frame), expected_gap)
            counters = ingress.metrics.snapshot()['counts']
            self.assertEqual(counters['source_queue_gap'], 2)
            self.assertEqual(counters['source_analysis_gap'], 1)

    def test_known_analysis_gap_does_not_add_region_dwell(self):
        for engine, attr in ((Intrusion(IntrusionConfig(enabled=True)), 'dwell'),
                             (Loitering(LoiteringConfig(enabled=True)), 'dwell_ns')):
            with self.subTest(engine=type(engine).__name__):
                for seq, (t, gap) in enumerate(((0.,False),(.1,False),(.3,True))):
                    f=FrameEnvelope('s','3','stream','run',1,seq,round(t*1e9),1,1280,720)
                    obs=DetectionObservation(f,'RFDETRMedium',0,.9,(.3,.3,.5,.6))
                    engine.evaluate(f,(obs,),(1,),gap_before=gap)
                value=getattr(engine.states[1],attr)
                self.assertAlmostEqual(value, .1 if attr=='dwell' else 100_000_000)


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