import json
import time
import unittest
from dataclasses import replace
from unittest.mock import Mock, patch

import numpy as np

from app.det_result_path import DetResultPath
from app.frame_ingress import FrameIngress
from app.intrusion import Intrusion, IntrusionConfig
from app.native_metadata import NativeFrame, NativeDetection
from app.metadata_publisher import MetadataPublisher, MetadataSettings
from app.metadata_render import render, HEADER
from app.byte_tracking import ByteTracker
from test_intrusion import frame
from test_metadata_connection import SettingsTest, wait_for


class ScenarioConnectionTest(unittest.TestCase):
    def test_actual_tracker_result_event_publisher_and_same_frame_render(self):
        engine = Intrusion(IntrusionConfig(enabled=True))
        publisher = MetadataPublisher(MetadataSettings('unused'), sink=Mock())
        path = DetResultPath(FrameIngress('server', 'source', 'stream', 'run', 1),
            ByteTracker(), 'RFDETRMedium', .5, Mock(), lambda: True, Mock(),
            publisher=publisher, intrusion=engine, display_threshold=.25,
            scenario_revision='a'*64)
        self.addCleanup(lambda: path.close(1))
        path.start()
        for i in range(6):
            f = frame(i, i*.1)
            copied = NativeFrame(i, f.pts_ns, 640, 360,
                (NativeDetection(0, .9, (.1, .1, .3, .8)),
                 NativeDetection(0, .4, (.6, .1, .8, .8))), 640, 360)
            path.submit(f, copied)
            wait_for(lambda: path.status().get('processed') == i+1)
        result = path.latest_result()
        self.assertEqual(len(result.events), 1)
        self.assertEqual([c.visible for c in result.commands], [True, False])
        self.assertEqual(result.commands[0].color, '#FF3030')
        self.assertEqual(result.events[0].frame, result.frame)
        wire = json.loads(next(iter(publisher._pending.values()))[1])
        self.assertEqual(wire, result.snapshot())
        self.assertEqual(wire['scenarioRevision'], 'a'*64)
        self.assertEqual(wire['events'][0]['eventId'], wire['objects'][0]['eventIds'][0])
        image, _ = render(result, np.zeros((360, 640, 3), np.uint8))
        self.assertEqual(image.getpixel((64, HEADER+150)), (255, 48, 48))
        path.close(1)
        self.assertFalse(path._thread.is_alive())
        self.assertFalse(engine.states)

    def test_no_frames_expiry_runs_on_existing_worker(self):
        now = [0.]
        engine = Intrusion(IntrusionConfig(enabled=True), clock=lambda: now[0])
        path = DetResultPath(FrameIngress('server', 'source', 'stream', 'run', 1),
            Mock(update=lambda obs: [1]*len(obs)), 'RFDETRMedium', .5, Mock(),
            lambda: True, Mock(), intrusion=engine)
        self.addCleanup(lambda: path.close(1))
        path.start()
        path.submit(frame(0, 0), NativeFrame(0, 0, 640, 360,
            (NativeDetection(0, .9, (.1, .1, .3, .8)),), 640, 360))
        wait_for(lambda: path.status().get('processed') == 1)
        now[0] = 31
        wait_for(lambda: not engine.states)


class IntrusionSettingsTest(SettingsTest):
    def test_intrusion_http_and_save_failure(self):
        from fastapi import FastAPI
        from fastapi.testclient import TestClient
        from app.api.streams import router
        from app.application.streams import StreamApplicationService
        from app.stream_config_store import ConfigPersistenceError
        app = FastAPI()
        app.include_router(router)
        with patch('app.api.streams.stream_service', StreamApplicationService(lambda: self.manager)), TestClient(app) as client:
            url = '/api/v1/streams/stream/settings'
            before = client.get(url).json()
            body = dict(expected_revision=before['configured']['revision'],
                        intrusion={'enabled': True, 'confidence': .6, 'roi': [[0, 0], [.5, 0], [.5, 1], [0, 1]]})
            with patch.object(self.manager.config_store, 'update', side_effect=ConfigPersistenceError()):
                self.assertEqual(client.patch(url, json=body).status_code, 503)
            self.assertEqual(client.get(url).json(), before)
            self.assertEqual(client.patch(url, json=dict(body, intrusion={'roi': []})).status_code, 409)
            value = client.patch(url, json=body)
            self.assertEqual(value.status_code, 200)
            self.assertTrue(value.json()['configured']['intrusion']['enabled'])
            self.assertEqual(self.g.state, 'stopped')
            self.assertEqual(client.patch(url, json=body).status_code, 409)


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