from collections import Counter, deque
from contextlib import contextmanager
from itertools import count
import queue
from types import SimpleNamespace
import unittest
from unittest.mock import Mock

from app.event_capture import EventCapture
from app.event_artifact_store import ArtifactStore
from tests.test_event_artifact import event
from tests.test_scenario_demo_sensitivity import configured_fall, configured_fight, down, pair


class DemoArtifactBoundaryTests(unittest.TestCase):
    def test_same_episode_uuid_and_single_capture_admission(self):
        for make, evaluate in [(configured_fall, down),
                               (lambda **kw: configured_fight(current_frame_fallback=True, **kw), pair)]:
            serial = count()
            scenario = make(event_id_factory=lambda: 'episode-'+str(next(serial)))
            capture = object.__new__(EventCapture)
            capture.native = SimpleNamespace(pull=lambda: None)
            capture.camera_id = 77
            capture.packets, capture.metadata = deque(), deque(maxlen=300)
            capture.results, capture.pending, capture.metrics = queue.Queue(), {}, Counter()
            capture.store = SimpleNamespace(accept=Mock(return_value=(True, None)))
            for i in range(61):
                rows, events = evaluate(scenario, i/10)
                flat = [r for row in rows for r in (row if isinstance(row, tuple) else (row,))]
                self.assertTrue(all(r.event_id == 'episode-0' for r in flat))
                capture.results.put(SimpleNamespace(events=events, snapshot=lambda: {'scenarioConfig': {}}))
                capture._collect()
            self.assertEqual(capture.store.accept.call_count, 1)
            self.assertEqual(list(capture.pending), ['episode-0'])

    def test_existing_event_is_rejected_before_new_artifact_reservation(self):
        store = ArtifactStore(SimpleNamespace())
        cur = Mock()
        cur.fetchone.side_effect = [{'reserved_jobs': 1}, {'exists': 1}]
        @contextmanager
        def transaction():
            yield cur
        store.transaction = transaction
        self.assertEqual(store.accept(event()), (False, 'DUPLICATE'))
        self.assertTrue(all(call.args[0].lstrip().startswith('SELECT') for call in cur.execute.call_args_list))
