"""
Controllable time mocking for testing.

This module provides utilities for controlling time in tests, allowing
deterministic testing of time-based game logic.
"""
from datetime import datetime, timedelta
from typing import Optional, Callable
import asyncio

class ControllableGameClock:
    """Mock clock that advances instantly"""

    def __init__(self, start_date: datetime = None):
        self.current_time = start_date or datetime(2000, 1, 1, 0, 0)
        self.tick_count = 0

    def advance_tick(self):
        """Advance by one game tick (1 minute)"""
        self.current_time += timedelta(minutes=1)
        self.tick_count += 1

    def advance_minutes(self, minutes: int):
        """Advance by N minutes"""
        self.current_time += timedelta(minutes=minutes)
        self.tick_count += minutes

    def advance_to_time(self, hour: int, minute: int = 0):
        """Fast-forward to specific time of day"""
        target = self.current_time.replace(hour=hour, minute=minute)
        if target < self.current_time:
            target += timedelta(days=1)

        diff = target - self.current_time
        minutes = int(diff.total_seconds() / 60)
        self.advance_minutes(minutes)

    def advance_to_date(self, date_str: str):
        """Fast-forward to specific date (format: 'YYYY-MM-DD')"""
        target = datetime.strptime(date_str, '%Y-%m-%d')
        target = target.replace(
            hour=self.current_time.hour,
            minute=self.current_time.minute
        )

        diff = target - self.current_time
        minutes = int(diff.total_seconds() / 60)
        if minutes > 0:
            self.advance_minutes(minutes)

    def get_time(self):
        """Get current game time"""
        return self.current_time

    async def sleep(self, duration: float):
        """Mock sleep (does nothing - instant)"""
        pass


class TimeMock:
    """
    Comprehensive time mocking utilities for tests.

    This class provides utilities for freezing time, controlling time progression,
    and simulating time-based scenarios in tests.
    """

    def __init__(self, initial_time: Optional[datetime] = None):
        """
        Initialize time mock.

        Args:
            initial_time: Initial datetime (defaults to 2024-01-01 00:00:00)
        """
        self._frozen_time = initial_time or datetime(2024, 1, 1, 0, 0, 0)
        self._is_frozen = False
        self._original_datetime = None
        self._callbacks: list[Callable] = []

    def freeze(self, dt: Optional[datetime] = None):
        """
        Freeze time to a specific datetime.

        Args:
            dt: Datetime to freeze to (uses current frozen time if None)
        """
        if dt is not None:
            self._frozen_time = dt
        self._is_frozen = True
        return self

    def unfreeze(self):
        """Unfreeze time, allowing it to progress normally."""
        self._is_frozen = False

    def advance(self, **kwargs):
        """
        Advance frozen time by a timedelta.

        Args:
            **kwargs: Keyword arguments for timedelta (days, hours, minutes, etc.)
        """
        self._frozen_time += timedelta(**kwargs)
        self._trigger_callbacks()

    def set_time(self, dt: datetime):
        """
        Set the frozen time to a specific datetime.

        Args:
            dt: Datetime to set
        """
        self._frozen_time = dt
        self._trigger_callbacks()

    def now(self) -> datetime:
        """
        Get the current time.

        Returns:
            datetime: Frozen time if frozen, otherwise real time
        """
        if self._is_frozen:
            return self._frozen_time
        return datetime.now()

    def add_callback(self, callback: Callable):
        """
        Add a callback to be called when time advances.

        Args:
            callback: Function to call on time advancement
        """
        self._callbacks.append(callback)

    def _trigger_callbacks(self):
        """Trigger all registered callbacks."""
        for callback in self._callbacks:
            callback(self._frozen_time)

    def advance_to_hour(self, hour: int, minute: int = 0):
        """
        Advance time to a specific hour of the current day.

        If the target time is earlier than current time, advances to next day.

        Args:
            hour: Hour (0-23)
            minute: Minute (0-59)
        """
        target = self._frozen_time.replace(hour=hour, minute=minute, second=0, microsecond=0)
        if target <= self._frozen_time:
            target += timedelta(days=1)
        self._frozen_time = target
        self._trigger_callbacks()

    def advance_to_date(self, year: int, month: int, day: int):
        """
        Advance time to a specific date.

        Args:
            year: Year
            month: Month (1-12)
            day: Day (1-31)
        """
        target = self._frozen_time.replace(year=year, month=month, day=day)
        if target < self._frozen_time:
            raise ValueError("Cannot advance to a date in the past")
        self._frozen_time = target
        self._trigger_callbacks()

    def get_date(self) -> datetime:
        """Get the current frozen date."""
        return self._frozen_time

    def get_hour(self) -> int:
        """Get the current hour."""
        return self._frozen_time.hour

    def get_minute(self) -> int:
        """Get the current minute."""
        return self._frozen_time.minute

    def get_day_of_week(self) -> int:
        """Get the day of week (0=Monday, 6=Sunday)."""
        return self._frozen_time.weekday()

    def is_weekend(self) -> bool:
        """Check if current day is a weekend."""
        return self._frozen_time.weekday() in (5, 6)  # Saturday or Sunday

    def __enter__(self):
        """Context manager entry."""
        self.freeze()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        self.unfreeze()
        return False

    def __repr__(self):
        status = "frozen" if self._is_frozen else "active"
        return f"TimeMock({status}, time={self._frozen_time.isoformat()})"
