"""Tests for Pip the Familiar — behavioral state machine."""

import time

import pytest

from timmy.familiar import _FIREPLACE_POS, Familiar, PipState


@pytest.fixture
def pip():
    return Familiar()


class TestInitialState:
    def test_starts_sleeping(self, pip):
        assert pip.state == PipState.SLEEPING

    def test_starts_calm(self, pip):
        assert pip.mood_mirror == "calm"

    def test_snapshot_returns_dict(self, pip):
        snap = pip.snapshot().to_dict()
        assert snap["name"] == "Pip"
        assert snap["state"] == "sleeping"
        assert snap["position"] == list(_FIREPLACE_POS)
        assert snap["mood_mirror"] == "calm"
        assert "state_duration_s" in snap


class TestAutoTransitions:
    def test_sleeping_to_waking_after_duration(self, pip):
        now = time.monotonic()
        # Force a short duration
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        result = pip.tick(now=now)
        assert result == PipState.WAKING

    def test_waking_to_wandering(self, pip):
        now = time.monotonic()
        pip._state = PipState.WAKING
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.WANDERING

    def test_wandering_to_bored(self, pip):
        now = time.monotonic()
        pip._state = PipState.WANDERING
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.BORED

    def test_bored_to_sleeping(self, pip):
        now = time.monotonic()
        pip._state = PipState.BORED
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.SLEEPING

    def test_full_cycle(self, pip):
        """Pip cycles: SLEEPING → WAKING → WANDERING → BORED → SLEEPING."""
        now = time.monotonic()
        expected = [
            PipState.WAKING,
            PipState.WANDERING,
            PipState.BORED,
            PipState.SLEEPING,
        ]
        for expected_state in expected:
            pip._duration = 0.1
            pip._entered_at = now - 1.0
            pip.tick(now=now)
            assert pip.state == expected_state
            now += 0.01

    def test_no_transition_before_duration(self, pip):
        now = time.monotonic()
        pip._duration = 100.0
        pip._entered_at = now
        pip.tick(now=now + 1.0)
        assert pip.state == PipState.SLEEPING


class TestEventReactions:
    def test_visitor_entered_wakes_pip(self, pip):
        assert pip.state == PipState.SLEEPING
        pip.on_event("visitor_entered")
        assert pip.state == PipState.WAKING

    def test_visitor_entered_while_wandering_investigates(self, pip):
        pip._state = PipState.WANDERING
        pip.on_event("visitor_entered")
        assert pip.state == PipState.INVESTIGATING

    def test_visitor_spoke_while_wandering_investigates(self, pip):
        pip._state = PipState.WANDERING
        pip.on_event("visitor_spoke")
        assert pip.state == PipState.INVESTIGATING

    def test_loud_event_wakes_sleeping_pip(self, pip):
        pip.on_event("loud_event")
        assert pip.state == PipState.WAKING

    def test_unknown_event_no_change(self, pip):
        pip.on_event("unknown_event")
        assert pip.state == PipState.SLEEPING

    def test_investigating_expires_to_bored(self, pip):
        now = time.monotonic()
        pip._state = PipState.INVESTIGATING
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.BORED


class TestMoodMirroring:
    def test_mood_mirrors_with_delay(self, pip):
        now = time.monotonic()
        pip.on_mood_change("curious", confidence=0.6, now=now)
        # Before delay — still calm
        pip.tick(now=now + 1.0)
        assert pip.mood_mirror == "calm"
        # After 3s delay — mirrors
        pip.tick(now=now + 4.0)
        assert pip.mood_mirror == "curious"

    def test_low_confidence_triggers_alert(self, pip):
        pip.on_mood_change("hesitant", confidence=0.2)
        assert pip.state == PipState.ALERT

    def test_energized_triggers_playful(self, pip):
        pip.on_mood_change("energized", confidence=0.7)
        assert pip.state == PipState.PLAYFUL

    def test_hesitant_low_confidence_triggers_hiding(self, pip):
        pip.on_mood_change("hesitant", confidence=0.35)
        assert pip.state == PipState.HIDING

    def test_special_state_not_from_non_interruptible(self, pip):
        pip._state = PipState.INVESTIGATING
        pip.on_mood_change("energized", confidence=0.7)
        # INVESTIGATING is not interruptible
        assert pip.state == PipState.INVESTIGATING


class TestSpecialStateRecovery:
    def test_alert_returns_to_wandering(self, pip):
        now = time.monotonic()
        pip._state = PipState.ALERT
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.WANDERING

    def test_playful_returns_to_wandering(self, pip):
        now = time.monotonic()
        pip._state = PipState.PLAYFUL
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.WANDERING

    def test_hiding_returns_to_waking(self, pip):
        now = time.monotonic()
        pip._state = PipState.HIDING
        pip._duration = 1.0
        pip._entered_at = now - 2.0
        pip.tick(now=now)
        assert pip.state == PipState.WAKING


class TestPositionHints:
    def test_sleeping_near_fireplace(self, pip):
        snap = pip.snapshot()
        assert snap.position == _FIREPLACE_POS

    def test_hiding_behind_desk(self, pip):
        pip.on_mood_change("hesitant", confidence=0.35)
        assert pip.state == PipState.HIDING
        snap = pip.snapshot()
        assert snap.position == (0.5, 0.3, -2.0)

    def test_playful_near_crystal_ball(self, pip):
        pip.on_mood_change("energized", confidence=0.7)
        snap = pip.snapshot()
        assert snap.position == (1.0, 1.2, 0.0)


class TestSingleton:
    def test_module_singleton_exists(self):
        from timmy.familiar import pip_familiar

        assert isinstance(pip_familiar, Familiar)
