import pytest
from app.fight_r5.display import DisplayHold
from app.fight_r5.display_policy import PolicyReader, read, write, validate


def body(order=0, tid=1, x=0):
    return dict(order=order, tid=tid, center=[x, 50], height=100)


def test_hold_boundary_and_no_extension():
    d = DisplayHold()
    assert d.step(0, [body()], [0], 3) == [0]
    assert d.step(2.99, [body()], [], 3) == [0]
    assert d.step(3.001, [body()], [], 3) == []
    assert d.step(3.1, [body()], [], 5) == []


def test_rehit_and_missing_partner():
    d = DisplayHold()
    d.step(0, [body(), body(1, 2, 200)], [0, 1], 3)
    assert d.step(.1, [body()], [], 3) == [0]
    assert d.step(.2, [], [], 3) == []
    assert d.step(.3, [body()], [0], 3) == [0]
    assert d.step(3.2, [body()], [], 3) == [0]


def test_identity_change_and_ambiguity():
    d = DisplayHold(); d.step(0, [body()], [0], 3)
    assert d.step(.1, [body(tid=None)], [], 3) == [0]
    assert d.step(.2, [body(tid=9)], [], 3) == [0]
    assert d.step(.3, [body(tid=None), body(1,None,1)], [], 3) == []


def test_zero_reverse_and_far():
    d = DisplayHold(); assert d.step(1,[body()],[0],0)==[0]
    assert d.step(1.1,[body()],[],0)==[]
    d.step(2,[body()],[0],3)
    assert d.step(2.1,[body(x=200)],[],3)==[]
    assert d.step(1,[body()],[],3)==[]


@pytest.mark.parametrize('value',[-1,6,True,None,float('nan'),float('inf'),'3'])
def test_invalid(value):
    with pytest.raises(ValueError):validate(dict(version=1,hold_seconds=value))


def test_persistence_reload_and_bad_update(tmp_path):
    p=tmp_path/'policy.json';now=[0.]
    reader=PolicyReader(p,clock=lambda:now[0])
    assert reader.get()==3
    write(p,5);now[0]=1.1;assert reader.get()==5
    assert PolicyReader(p).get()==5
    stamp=p.stat().st_mtime_ns;write(p,5);assert p.stat().st_mtime_ns==stamp
    with pytest.raises(ValueError):write(p,6)
    assert read(p)['hold_seconds']==5
    p.write_text('bad');now[0]=2.2;assert reader.get()==5
    assert reader.error


def test_runtime_display_does_not_create_events(tmp_path, monkeypatch):
    from dataclasses import replace
    from unittest.mock import patch
    from app.fight_runtime import FightScenario, FightSettings
    from test_fight import frame, person
    p=tmp_path/'policy.json';write(p,3)
    monkeypatch.setenv('CONTEXTRA_FIGHT_DISPLAY_POLICY',str(p))
    engine=FightScenario(FightSettings.parse({'enabled':True,'engine':'r5'}))
    def step(t,hit):
        f=replace(frame(t),frame_seq=int(t*100)+1)
        result=dict(hit=hit,current_orders=[0,1] if hit else [],display_orders=[],episode={})
        with patch.object(engine.engine.core,'step',return_value=(result,{})):
            return engine.evaluate(f,(person(f,450),person(f,650)),(1,2))
    assert len(step(0,True)[1])==1
    rows,events=step(2.9,False)
    assert rows[0][0].state=='FIGHT' and not events
    assert step(3.01,False)[0][0][0].state=='MONITORING'
    engine.clear();assert not engine.engine.display_hold.states
