"""HEL-977: counting the 25 places (pure rules, no database)."""

from datetime import UTC, datetime, timedelta

from app.meetings.models import MeetingDevice, MemberRole
from app.meetings.places import MAX_PLACES, Places, _Person, device_in_use

NOW = datetime(2026, 11, 3, 12, 0, tzinfo=UTC)


def _places(**people: _Person) -> Places:
    return Places(NOW, dict(people))


def _connected(*identities: str) -> set[str]:
    return set(identities)


def test_each_connected_device_counts():
    # BR-06.1: one person on two devices takes two places.
    places = _places(ana=_Person(role=MemberRole.PARTICIPANT, in_use=_connected("a1", "a2")))

    assert places.in_use() == 2


def test_waiting_people_take_no_place():
    # BR-06.4: someone waiting has no member role, devices or admission yet.
    places = _places(waiting=_Person(role=MemberRole.PARTICIPANT))

    assert places.in_use() == 0


def test_host_and_co_host_places_are_held_all_meeting():
    # BR-06.5: away for any length of time, still one place each.
    places = _places(
        host=_Person(role=MemberRole.HOST, last_left=NOW - timedelta(hours=1)),
        co=_Person(role=MemberRole.CO_HOST),
    )

    assert places.in_use() == 2
    assert places.is_held("host") and places.is_held("co")


def test_held_place_covers_one_device_only():
    host = _Person(role=MemberRole.HOST)
    places = _places(host=host)

    # Coming back on one device uses the held place...
    assert places.in_use(add_device=("host", "h1")) == 1
    # ...a second device needs a place of its own.
    host.in_use = _connected("h1")
    assert places.in_use(add_device=("host", "h2")) == 2


def test_admitted_participant_place_is_held_for_2_minutes_after_dropping():
    recent = _places(p=_Person(role=MemberRole.PARTICIPANT, last_left=NOW - timedelta(seconds=90)))
    old = _places(p=_Person(role=MemberRole.PARTICIPANT, last_left=NOW - timedelta(seconds=150)))

    assert recent.in_use() == 1 and recent.is_in("p")
    assert old.in_use() == 0 and not old.is_in("p")


def test_just_admitted_participant_place_is_held_for_2_minutes():
    recent = _places(p=_Person(role=MemberRole.PARTICIPANT, admitted_at=NOW - timedelta(minutes=1)))
    old = _places(p=_Person(role=MemberRole.PARTICIPANT, admitted_at=NOW - timedelta(minutes=3)))

    assert recent.is_in("p") and recent.in_use() == 1
    assert not old.is_in("p") and old.in_use() == 0


def test_blocked_people_hold_nothing():
    places = _places(b=_Person(role=MemberRole.PARTICIPANT, admitted_at=NOW, blocked=True))

    assert places.in_use() == 0
    assert not places.is_in("b")


def test_room_for_one_more_up_to_25():
    people = {f"u{i}": _Person(role=MemberRole.PARTICIPANT, in_use={f"d{i}"}) for i in range(24)}
    places = Places(NOW, people)

    assert places.has_room_for_person("newcomer")
    people["u24"] = _Person(role=MemberRole.PARTICIPANT, in_use={"d24"})
    assert places.in_use() == MAX_PLACES
    assert not places.has_room_for_person("newcomer")
    # Replacing a device that is already in use needs no new place.
    assert places.has_room_for_device("u0", "d0")
    assert not places.has_room_for_device("u0", "another-device")


def _device(**fields) -> MeetingDevice:
    defaults = {"pass_issued_at": NOW - timedelta(minutes=1)}
    return MeetingDevice(**(defaults | fields))


def test_device_holds_a_place_from_its_pass_until_it_connects_or_the_pass_runs_out():
    assert device_in_use(_device(), NOW)  # pass issued, not connected yet
    assert not device_in_use(_device(pass_issued_at=NOW - timedelta(minutes=11)), NOW)
    assert device_in_use(_device(connected_at=NOW), NOW)  # connected
    assert not device_in_use(
        _device(
            pass_issued_at=NOW - timedelta(minutes=5),
            connected_at=NOW - timedelta(minutes=4),
            left_at=NOW,
        ),
        NOW,
    )  # left
    assert device_in_use(
        _device(connected_at=NOW - timedelta(minutes=4), left_at=NOW - timedelta(minutes=2)), NOW
    )  # left, then got a new pass to come back
