"""
Integration tests for BaoLife game loop.

Tests the full game loop execution with mocks to ensure the core
game simulation works correctly.
"""
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))

import pytest
from cli_runner import HeadlessGame
from tests.fixtures.player_fixtures import (
    create_newborn_player, create_student_player, create_adult_player
)


class TestGameLoopBasics:
    """Basic game loop functionality tests."""

    def test_game_loop_single_tick(self):
        """Test that game loop can execute a single tick."""
        player = create_newborn_player()

        with HeadlessGame(player=player) as game:
            initial_ticks = game.player.ticks

            game.tick(1)

            assert game.player.ticks > initial_ticks

    def test_game_loop_multiple_ticks(self):
        """Test that game loop can execute multiple ticks."""
        player = create_newborn_player()

        with HeadlessGame(player=player) as game:
            initial_ticks = game.player.ticks

            game.tick(10)

            assert game.player.ticks >= initial_ticks + 10

    def test_game_loop_time_progression(self):
        """Test that time progresses correctly."""
        player = create_newborn_player()
        player.minuteOfHour = 0
        player.hourOfDay = 10

        with HeadlessGame(player=player) as game:
            # Advance 30 minutes
            game.advance_minutes(30)

            assert game.player.minuteOfHour == 30

    def test_game_loop_hour_rollover(self):
        """Test that hours roll over correctly."""
        player = create_newborn_player()
        player.minuteOfHour = 50
        player.hourOfDay = 10

        with HeadlessGame(player=player) as game:
            # Advance 20 minutes (should roll to hour 11)
            game.advance_minutes(20)

            assert game.player.hourOfDay == 11
            assert game.player.minuteOfHour == 10

    def test_game_loop_day_rollover(self):
        """Test that days roll over correctly."""
        player = create_newborn_player()
        player.hourOfDay = 23
        player.minuteOfHour = 50
        player.dayOfYear = 100

        with HeadlessGame(player=player) as game:
            # Advance to next day
            game.advance_minutes(20)

            assert game.player.hourOfDay == 0
            assert game.player.dayOfYear == 101


class TestGameLoopStats:
    """Tests for stat updates during game loop."""

    def test_age_increases_daily(self):
        """Test that age increases daily."""
        player = create_newborn_player()
        player.c.ageDays = 10
        # Set ageHours to 239 so that after 1 hour it becomes 240 (240 % 24 == 0)
        # which triggers ageDays increment
        player.c.ageHours = 239
        player.hourOfDay = 23

        with HeadlessGame(player=player) as game:
            # Advance 2 hours (crossing midnight at hour 0 will trigger age increase)
            # Hour 23->0: ageHours becomes 240, triggers ageDays increment
            # Hour 0->1: ageHours becomes 241
            game.advance_hours(2)

            assert game.player.c.ageDays == 11

    def test_energy_updates(self):
        """Test that energy updates during gameplay."""
        player = create_adult_player()
        player.c.energy = 50

        with HeadlessGame(player=player) as game:
            initial_energy = game.player.c.energy

            # Run for a while
            game.advance_hours(1)

            # Energy should have changed (or at least stayed within bounds)
            assert 0 <= game.player.c.energy <= 100

    def test_hunger_increases(self):
        """Test that hunger system is working (fluctuates over time)."""
        player = create_adult_player()
        player.c.hunger = 50
        player.c.weightType = 'Normal'

        with HeadlessGame(player=player) as game:
            initial_hunger = game.player.c.hunger

            # Run for several hours - hunger fluctuates with rand(-2, 2) each hour
            game.advance_hours(6)

            # Hunger should stay within bounds (0-100) and may have changed
            assert 0 <= game.player.c.hunger <= 100
            # After 6 hours with random changes, hunger likely changed
            # (though theoretically could stay the same with right random sequence)


class TestGameLoopEvents:
    """Tests for event handling during game loop."""

    def test_events_collected(self):
        """Test that events are collected during gameplay."""
        player = create_newborn_player()
        player.c.ageDays = 0
        player.c.firstname = "TestBaby"

        with HeadlessGame(player=player) as game:
            # Advance to trigger first day message
            game.advance_hours(24)

            # Check if any events were triggered
            events = game.get_events()
            # Events may or may not trigger, but system should work
            assert isinstance(events, list)

    def test_output_collector_works(self):
        """Test that output collector captures game output."""
        player = create_adult_player()

        with HeadlessGame(player=player) as game:
            game.advance_hours(1)

            # Should be able to get player updates
            updates = game.get_player_updates()
            assert isinstance(updates, list)


class TestGameLoopAdvancement:
    """Tests for time advancement methods."""

    def test_advance_to_hour(self):
        """Test advancing to specific hour."""
        player = create_newborn_player()
        player.hourOfDay = 8

        with HeadlessGame(player=player) as game:
            game.advance_to_hour(12)

            assert game.player.hourOfDay == 12

    def test_advance_days(self):
        """Test advancing multiple days."""
        player = create_newborn_player()
        initial_day = player.dayOfYear

        with HeadlessGame(player=player) as game:
            game.advance_days(3)

            expected_day = (initial_day + 3) % 365
            if expected_day == 0:
                expected_day = 365

            assert game.player.dayOfYear == expected_day

    def test_advance_hours(self):
        """Test advancing multiple hours."""
        player = create_newborn_player()
        player.hourOfDay = 10
        player.minuteOfHour = 0

        with HeadlessGame(player=player) as game:
            game.advance_hours(5)

            assert game.player.hourOfDay == 15


class TestGameLoopPersistence:
    """Tests for game state persistence."""

    def test_save_game(self):
        """Test saving game state."""
        player = create_adult_player()

        with HeadlessGame(player=player) as game:
            game.advance_hours(1)
            game.save()

            # Game should be saved in storage
            assert game.storage.game_exists(game.player.userID)

    def test_load_saved_game(self):
        """Test loading a saved game."""
        from tests.mocks.storage_mock import InMemoryStorage

        player = create_adult_player()
        storage = InMemoryStorage()

        # Save a game
        game1 = HeadlessGame(player=player)
        game1.storage = storage  # Use shared storage
        game1.advance_hours(5)
        game1.save()
        saved_hour = game1.player.hourOfDay
        user_id = game1.player.userID
        game1.close()

        # Load the saved game manually
        loaded_player = storage.load_game(user_id)
        assert loaded_player is not None, "Game should be saved in storage"

        # Create new game with loaded player
        game2 = HeadlessGame(player=loaded_player)
        game2.storage = storage  # Use same storage instance

        # Should have the same state
        assert hasattr(game2.player, 'hourOfDay')
        assert game2.player.hourOfDay == saved_hour
        game2.close()
