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

from app.byte_tracking import TrackingSettings
from app.stream_settings import patch_settings, settings_view
from test_det_result_path import Collector, envelope, native, wait_for
from app.det_result_path import DetResultPath
from app.frame_ingress import FrameIngress
from app.intrusion import Intrusion, IntrusionConfig
from test_metadata_connection import SettingsTest


class DisabledTrackingTest(unittest.TestCase):
    def test_default_validation_and_combined_patch(self):
        spec = dict(stream_id='stream', server_id='server', source_id='camera')
        self.assertTrue(settings_view(spec)['tracking']['enabled'])
        self.assertEqual(patch_settings(spec, {'tracking': {'enabled': True}}), spec)
        on = dict(spec, intrusion={'enabled': True, 'confidence': .5})
        with self.assertRaises(ValueError):
            patch_settings(on, {'tracking': {'enabled': False}})
        off = patch_settings(on, {'tracking': {'enabled': False}, 'intrusion': {'enabled': False}})
        self.assertFalse(settings_view(off)['tracking']['enabled'])
        self.assertEqual(off['intrusion']['confidence'], .5)
        for value in (None, {'enabled': 0}, {'enabled': 'false'}, {'unknown': 1}):
            with self.assertRaises(ValueError):
                patch_settings(spec, {'tracking': value})
        with self.assertRaises(ValueError):
            patch_settings({}, {'tracking': {'enabled': False}})

    def test_off_worker_preserves_objects_empty_and_stale(self):
        ingress = FrameIngress('server','camera','stream','run',1)
        active = [True]; errors = []
        path = DetResultPath(ingress, None, 'RFDETRMedium', .5, Collector(),
                             lambda: active[0], lambda: errors.append(True), display_threshold=.25)
        try:
            with patch('app.tracking_kernel.TRACKING_KERNEL.turn', side_effect=AssertionError('FIFO')):
                path.start();path.submit(envelope(), native())
                wait_for(lambda: path.status().get('processed') == 1)
                obj = path.snapshot()['objects'][0]
                self.assertIsNone(obj['trackId'])
                self.assertEqual(obj['confidence'], .9)
                path.submit(envelope(1), native(empty=True))
                wait_for(lambda: path.status().get('processed') == 2)
                self.assertEqual(path.snapshot()['objects'], [])
                active[0] = False
                self.assertFalse(path.submit(envelope(2), native()))
                with self.assertRaises(ValueError):
                    path.submit(envelope(3, 'foreign', 2), native())
        finally:
            path.close(2)
        self.assertEqual(errors, [])
        self.assertFalse(path._thread.is_alive())
        self.assertIsNone(path.snapshot())

    def test_factory_off_does_not_construct_tracker_and_resets(self):
        from app.stream_manager import StreamGroup
        group = StreamGroup(dict(stream_id='stream',source_id='camera',server_id='server',store=False,
                                 tracking={'enabled': False}))
        profile = SimpleNamespace(roles=('det',), resolved_models={'det':'RFDETRMedium'}, score_threshold=.5)
        with patch('app.byte_tracking.ByteTracker', side_effect=AssertionError('created Tracker')), \
             patch('app.frame_ingress.require_identity_binding'), patch.object(group, '_begin'):
            for _ in range(2):
                generation = group.start()
                path = group._prepare_det_results(profile, generation, group._run_stop_event, Collector())
                self.assertIsNone(path.tracker)
                group.stop()
                self.assertTrue(group._cleanup_done.wait(2))
                self.assertIsNone(group.result_path)
        with self.assertRaises(ValueError):
            DetResultPath(FrameIngress('s','c','t','r',1), None, 'RFDETRMedium', .5, Collector(),
                          lambda: True, lambda: None, intrusion=Intrusion(IntrusionConfig(enabled=True)))


class DisabledSettingsTest(SettingsTest):
    def test_api_cas_failure_noop(self):
        from app.api.streams import SettingsPatch, patch_settings as route
        from app.stream_config_store import ConfigPersistenceError
        view = self.manager.settings('stream'); rev = view['configured']['revision']
        request = SettingsPatch(expected_revision=rev, tracking={'enabled': False}, intrusion={'enabled': False})
        with patch.object(self.manager.config_store,'update',side_effect=ConfigPersistenceError()):
            with self.assertRaises(ConfigPersistenceError):
                self.manager.patch_settings('stream',rev,{'tracking':{'enabled':False}})
        self.assertEqual(view,self.manager.settings('stream'))
        with patch('app.api.streams.stream_service',self.manager):
            result=route('stream',request)
        self.assertFalse(result['configured']['tracking']['enabled'])
        with self.assertRaises(ValueError):
            self.manager.patch_settings('stream',rev,{'tracking':{'enabled':True}})
        with patch.object(self.manager.config_store,'update',side_effect=AssertionError('no-op write')):
            self.manager.patch_settings('stream',result['configured']['revision'],{'tracking':{'enabled':False}})
