"""Tests for NPC schedule system."""

import time
from pathlib import Path

import pytest

from mudlib.entity import Mob
from mudlib.gametime import GameTime, get_game_hour
from mudlib.mobs import load_mob_template, mob_templates, mobs, spawn_mob
from mudlib.npc_schedule import (
    NpcSchedule,
    ScheduleEntry,
    process_schedules,
)
from mudlib.zone import Zone


@pytest.fixture(autouse=True)
def clear_mobs():
    """Clear mobs list before and after each test."""
    mobs.clear()
    mob_templates.clear()
    yield
    mobs.clear()
    mob_templates.clear()


@pytest.fixture(autouse=True)
def _reset_globals():
    yield
    import mudlib.gametime

    mudlib.gametime._game_time = None


def test_schedule_entry_creation():
    """ScheduleEntry can be created with required fields."""
    entry = ScheduleEntry(hour=6, state="working")
    assert entry.hour == 6
    assert entry.state == "working"
    assert entry.location is None
    assert entry.data is None


def test_schedule_entry_with_location():
    """ScheduleEntry can include optional location."""
    entry = ScheduleEntry(hour=10, state="patrol", location={"x": 5, "y": 10})
    assert entry.hour == 10
    assert entry.state == "patrol"
    assert entry.location == {"x": 5, "y": 10}


def test_schedule_entry_with_data():
    """ScheduleEntry can include optional behavior data."""
    waypoints = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
    entry = ScheduleEntry(hour=8, state="patrol", data={"waypoints": waypoints})
    assert entry.hour == 8
    assert entry.state == "patrol"
    assert entry.data == {"waypoints": waypoints}


def test_npc_schedule_get_active_entry():
    """NpcSchedule.get_active_entry returns correct entry for a given hour."""
    entries = [
        ScheduleEntry(hour=6, state="working"),
        ScheduleEntry(hour=12, state="idle"),
        ScheduleEntry(hour=18, state="patrol"),
    ]
    schedule = NpcSchedule(entries=entries)

    # At hour 6, return first entry
    assert schedule.get_active_entry(6).state == "working"
    # Between entries, return most recent
    assert schedule.get_active_entry(8).state == "working"
    assert schedule.get_active_entry(12).state == "idle"
    assert schedule.get_active_entry(15).state == "idle"
    assert schedule.get_active_entry(18).state == "patrol"
    assert schedule.get_active_entry(20).state == "patrol"


def test_npc_schedule_wraps_around_midnight():
    """NpcSchedule.get_active_entry wraps around midnight correctly."""
    entries = [
        ScheduleEntry(hour=6, state="working"),
        ScheduleEntry(hour=22, state="idle"),
    ]
    schedule = NpcSchedule(entries=entries)

    # Before first entry, wrap to last entry
    assert schedule.get_active_entry(0).state == "idle"
    assert schedule.get_active_entry(2).state == "idle"
    assert schedule.get_active_entry(5).state == "idle"
    # After first entry, use it
    assert schedule.get_active_entry(6).state == "working"
    assert schedule.get_active_entry(10).state == "working"
    # After last entry
    assert schedule.get_active_entry(22).state == "idle"
    assert schedule.get_active_entry(23).state == "idle"


def test_process_schedules_transitions_state(monkeypatch):
    """process_schedules transitions mob state when hour changes."""
    zone = Zone(name="test", width=20, height=20)
    mob = Mob(
        name="librarian",
        location=zone,
        x=5,
        y=5,
        behavior_state="idle",
    )
    entries = [
        ScheduleEntry(hour=6, state="working"),
        ScheduleEntry(hour=18, state="idle"),
    ]
    mob.schedule = NpcSchedule(entries=entries)
    mobs.append(mob)

    # At hour 6, transition to working
    process_schedules(mobs, 6)
    assert mob.behavior_state == "working"

    # At hour 18, transition to idle
    process_schedules(mobs, 18)
    assert mob.behavior_state == "idle"


def test_process_schedules_moves_mob_to_location(monkeypatch):
    """process_schedules moves mob to scheduled location."""
    zone = Zone(name="test", width=20, height=20)
    mob = Mob(
        name="guard",
        location=zone,
        x=0,
        y=0,
        behavior_state="idle",
    )
    entries = [
        ScheduleEntry(hour=8, state="patrol", location={"x": 10, "y": 5}),
    ]
    mob.schedule = NpcSchedule(entries=entries)
    mobs.append(mob)

    # Process at hour 8
    process_schedules(mobs, 8)
    assert mob.x == 10
    assert mob.y == 5
    assert mob.behavior_state == "patrol"


def test_process_schedules_sets_behavior_data(monkeypatch):
    """process_schedules sets behavior_data from schedule entry."""
    zone = Zone(name="test", width=20, height=20)
    mob = Mob(
        name="guard",
        location=zone,
        x=0,
        y=0,
        behavior_state="idle",
    )
    waypoints = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
    entries = [
        ScheduleEntry(hour=10, state="patrol", data={"waypoints": waypoints}),
    ]
    mob.schedule = NpcSchedule(entries=entries)
    mobs.append(mob)

    # Process at hour 10
    process_schedules(mobs, 10)
    assert mob.behavior_state == "patrol"
    # behavior_data includes the schedule data
    assert mob.behavior_data["waypoints"] == waypoints
    # last hour is tracked on the mob directly
    assert mob.schedule_last_hour == 10


def test_process_schedules_doesnt_reprocess_same_hour(monkeypatch):
    """process_schedules doesn't re-process the same hour."""
    zone = Zone(name="test", width=20, height=20)
    mob = Mob(
        name="npc",
        location=zone,
        x=0,
        y=0,
        behavior_state="idle",
    )
    entries = [
        ScheduleEntry(hour=6, state="working", location={"x": 10, "y": 10}),
    ]
    mob.schedule = NpcSchedule(entries=entries)
    mobs.append(mob)

    # First process at hour 6
    process_schedules(mobs, 6)
    assert mob.x == 10
    assert mob.y == 10
    assert mob.behavior_state == "working"

    # Move mob away manually
    mob.x = 5
    mob.y = 5
    mob.behavior_state = "idle"

    # Process again at hour 6 (should not re-apply)
    process_schedules(mobs, 6)
    assert mob.x == 5  # Should not move back
    assert mob.y == 5
    assert mob.behavior_state == "idle"  # Should not transition back


def test_game_time_returns_valid_hour():
    """GameTime.get_game_hour returns hour in range 0-23."""
    game_time = GameTime(epoch=time.time(), real_minutes_per_game_hour=1.0)
    hour = game_time.get_game_hour()
    assert 0 <= hour < 24


def test_game_time_advances_correctly():
    """GameTime advances correctly based on real time."""
    # Start at a known epoch
    epoch = time.time()
    game_time = GameTime(epoch=epoch, real_minutes_per_game_hour=1.0)

    # At epoch, should be hour 0
    hour_at_epoch = game_time.get_game_hour()

    # Simulate time passing by creating a new GameTime with earlier epoch
    # (1 minute ago = 1 game hour ago)
    earlier_epoch = epoch - 60  # 1 minute ago
    game_time_earlier = GameTime(epoch=earlier_epoch, real_minutes_per_game_hour=1.0)
    hour_after_one_minute = game_time_earlier.get_game_hour()

    # Hour should have advanced by 1
    expected_hour = (hour_at_epoch + 1) % 24
    assert hour_after_one_minute == expected_hour


def test_load_schedule_from_toml(tmp_path: Path):
    """Schedule data can be loaded from mob TOML template."""
    # Create a temp TOML file with schedule
    toml_content = """
name = "librarian"
description = "a studious librarian"
pl = 100.0
stamina = 100.0
max_stamina = 100.0
moves = []

[[schedule]]
hour = 6
state = "working"
location = {x = 10, y = 5}

[[schedule]]
hour = 20
state = "patrol"
waypoints = [{x = 10, y = 5}, {x = 12, y = 5}, {x = 12, y = 7}]

[[schedule]]
hour = 22
state = "idle"
"""
    toml_file = tmp_path / "librarian.toml"
    toml_file.write_text(toml_content)

    # Load template
    template = load_mob_template(toml_file)
    assert template.name == "librarian"
    assert template.schedule is not None
    assert len(template.schedule.entries) == 3

    # Check first entry
    entry1 = template.schedule.entries[0]
    assert entry1.hour == 6
    assert entry1.state == "working"
    assert entry1.location == {"x": 10, "y": 5}

    # Check second entry
    entry2 = template.schedule.entries[1]
    assert entry2.hour == 20
    assert entry2.state == "patrol"
    assert entry2.data == {
        "waypoints": [{"x": 10, "y": 5}, {"x": 12, "y": 5}, {"x": 12, "y": 7}]
    }

    # Check third entry
    entry3 = template.schedule.entries[2]
    assert entry3.hour == 22
    assert entry3.state == "idle"


def test_schedule_carries_to_spawned_mob(tmp_path: Path):
    """Schedule carries through from template to spawned mob."""
    # Create a temp TOML file with schedule
    toml_content = """
name = "guard"
description = "a watchful guard"
pl = 100.0
stamina = 100.0
max_stamina = 100.0
moves = []

[[schedule]]
hour = 8
state = "patrol"
location = {x = 5, y = 5}
"""
    toml_file = tmp_path / "guard.toml"
    toml_file.write_text(toml_content)

    # Load template and spawn mob
    template = load_mob_template(toml_file)
    zone = Zone(name="test", width=20, height=20)
    mob = spawn_mob(template, x=0, y=0, zone=zone)

    assert mob.schedule is not None
    assert len(mob.schedule.entries) == 1
    assert mob.schedule.entries[0].hour == 8
    assert mob.schedule.entries[0].state == "patrol"


def test_get_game_hour_convenience_function():
    """get_game_hour() returns current game hour from global instance."""
    from mudlib.gametime import init_game_time

    # Initialize game time for this test
    init_game_time()
    hour = get_game_hour()
    assert 0 <= hour < 24


def test_process_schedules_skips_converse_state():
    """process_schedules skips mobs in converse state to preserve conversation."""
    zone = Zone(name="test", width=20, height=20)
    mob = Mob(
        name="conversing_npc",
        location=zone,
        x=5,
        y=5,
        behavior_state="converse",
    )
    entries = [
        ScheduleEntry(hour=6, state="working", location={"x": 10, "y": 10}),
    ]
    mob.schedule = NpcSchedule(entries=entries)
    mobs.append(mob)

    # Process at hour 6 (schedule would normally transition to working)
    process_schedules(mobs, 6)

    # Mob should still be in converse state, not moved
    assert mob.behavior_state == "converse"
    assert mob.x == 5
    assert mob.y == 5
    # schedule_last_hour should not be set (processing was skipped)
    assert mob.schedule_last_hour is None
