import itertools
from dataclasses import replace
import queue
import threading
from collections import Counter
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from app.fight_runtime import FightSettings,FightScenario
from app.event_cooldown import EventCooldown
from app.event_capture import EventCapture
from test_fight import frame,person


class NotificationRuntimeTests(unittest.TestCase):
    def test_clock_gate_rearm_hold_and_failure(self):
        ids=iter(('one','two','three'))
        engine=FightScenario(FightSettings.parse({'enabled':True,'engine':'r5'}),event_id_factory=lambda:next(ids))
        clock=[0.];gate=EventCooldown(10,clock=lambda:clock[0]);engine.engine.event_admission=gate.allow
        sequence=itertools.count()
        def step(t,hit,hold=False):
            clock[0]=t;f=replace(frame(t),frame_seq=next(sequence))
            result=dict(hit=hit,current_orders=[0,1] if hit else [],display_orders=[0,1] if hit or hold else [],episode=dict(event=False,latched=True))
            with patch.object(engine.engine.core,'step',return_value=(result,{})):
                return engine.evaluate(f,(person(f,450),person(f,650)),(1,2))
        _,first=step(0,True)
        self.assertEqual(len(first),1)
        capture=object.__new__(EventCapture)
        capture.error=None;capture.stop=threading.Event();capture.results=queue.Queue(maxsize=1);capture.metrics=Counter()
        capture.results.put(object())
        self.assertFalse(capture.submit(SimpleNamespace(events=first)))
        self.assertEqual(capture.metrics['unaccepted_events'],1)
        rows,events=step(1,False,True)
        self.assertFalse(events);self.assertEqual(rows[0][0].state,'FIGHT')
        step(2,False)
        self.assertFalse(engine.engine.notification.latched)
        self.assertFalse(step(3,True)[1])
        self.assertFalse(engine.engine.notification.latched)
        self.assertEqual(len(step(10,True)[1]),1)
        self.assertFalse(step(19.999,True)[1])
        self.assertEqual(len(step(20,True)[1]),1)
        self.assertEqual(gate.metrics['FIGHT_admitted'],3)
        engine.clear()
        self.assertIsNone(engine.engine.notification.last_hit)

    def test_no_scheduled_event(self):
        engine=FightScenario(FightSettings.parse({'enabled':True,'engine':'r5'}))
        value=dict(hit=False,current_orders=[],display_orders=[],episode=dict(event=True,latched=True))
        f=frame(100)
        with patch.object(engine.engine.core,'step',return_value=(value,{})):
            self.assertFalse(engine.evaluate(f,(person(f,450),),(1,))[1])
