"""Tests for the time adapter — circadian awareness."""

from datetime import UTC, datetime
from unittest.mock import AsyncMock, patch

import pytest

from timmy.adapters.time_adapter import TimeAdapter, classify_period

# ---------- classify_period ----------


@pytest.mark.parametrize(
    "hour, expected",
    [
        (6, "morning"),
        (7, "morning"),
        (8, "morning"),
        (9, None),
        (12, "afternoon"),
        (13, "afternoon"),
        (14, None),
        (18, "evening"),
        (19, "evening"),
        (20, None),
        (23, "late_night"),
        (0, "late_night"),
        (2, "late_night"),
        (3, None),
        (10, None),
        (16, None),
    ],
)
def test_classify_period(hour: int, expected: str | None) -> None:
    assert classify_period(hour) == expected


# ---------- record_interaction / time_since ----------


def test_time_since_last_interaction_none() -> None:
    adapter = TimeAdapter()
    assert adapter.time_since_last_interaction() is None


def test_time_since_last_interaction() -> None:
    adapter = TimeAdapter()
    t0 = datetime(2026, 3, 18, 10, 0, 0, tzinfo=UTC)
    t1 = datetime(2026, 3, 18, 10, 5, 0, tzinfo=UTC)
    adapter.record_interaction(now=t0)
    assert adapter.time_since_last_interaction(now=t1) == 300.0


# ---------- tick — circadian events ----------


@pytest.mark.asyncio
async def test_tick_emits_morning() -> None:
    adapter = TimeAdapter()
    now = datetime(2026, 3, 18, 7, 0, 0, tzinfo=UTC)

    with patch("timmy.adapters.time_adapter.emit", new_callable=AsyncMock) as mock_emit:
        emitted = await adapter.tick(now=now)

    assert "time.morning" in emitted
    mock_emit.assert_any_call(
        "time.morning",
        source="time_adapter",
        data={"hour": 7, "period": "morning"},
    )


@pytest.mark.asyncio
async def test_tick_emits_late_night() -> None:
    adapter = TimeAdapter()
    now = datetime(2026, 3, 19, 1, 0, 0, tzinfo=UTC)

    with patch("timmy.adapters.time_adapter.emit", new_callable=AsyncMock) as mock_emit:
        emitted = await adapter.tick(now=now)

    assert "time.late_night" in emitted
    mock_emit.assert_any_call(
        "time.late_night",
        source="time_adapter",
        data={"hour": 1, "period": "late_night"},
    )


@pytest.mark.asyncio
async def test_tick_no_duplicate_period() -> None:
    """Same period on consecutive ticks should not re-emit."""
    adapter = TimeAdapter()
    t1 = datetime(2026, 3, 18, 7, 0, 0, tzinfo=UTC)
    t2 = datetime(2026, 3, 18, 7, 30, 0, tzinfo=UTC)

    with patch("timmy.adapters.time_adapter.emit", new_callable=AsyncMock):
        await adapter.tick(now=t1)
        emitted = await adapter.tick(now=t2)

    assert emitted == []


@pytest.mark.asyncio
async def test_tick_no_event_outside_periods() -> None:
    adapter = TimeAdapter()
    now = datetime(2026, 3, 18, 10, 0, 0, tzinfo=UTC)

    with patch("timmy.adapters.time_adapter.emit", new_callable=AsyncMock) as mock_emit:
        emitted = await adapter.tick(now=now)

    assert emitted == []
    mock_emit.assert_not_called()


# ---------- tick — new_day ----------


@pytest.mark.asyncio
async def test_tick_emits_new_day() -> None:
    adapter = TimeAdapter()
    day1 = datetime(2026, 3, 18, 23, 30, 0, tzinfo=UTC)
    day2 = datetime(2026, 3, 19, 0, 30, 0, tzinfo=UTC)

    with patch("timmy.adapters.time_adapter.emit", new_callable=AsyncMock) as mock_emit:
        await adapter.tick(now=day1)
        emitted = await adapter.tick(now=day2)

    assert "time.new_day" in emitted
    mock_emit.assert_any_call(
        "time.new_day",
        source="time_adapter",
        data={"date": "2026-03-19"},
    )


@pytest.mark.asyncio
async def test_tick_no_new_day_same_date() -> None:
    adapter = TimeAdapter()
    t1 = datetime(2026, 3, 18, 10, 0, 0, tzinfo=UTC)
    t2 = datetime(2026, 3, 18, 15, 0, 0, tzinfo=UTC)

    with patch("timmy.adapters.time_adapter.emit", new_callable=AsyncMock):
        await adapter.tick(now=t1)
        emitted = await adapter.tick(now=t2)

    assert "time.new_day" not in emitted
