"""
Game Loop Manager

This module contains the core game simulation loop and iteration utilities.
It handles:
- Main game loop (initLifeSim): Processes each game tick, updates player state,
  checks events, and manages time progression
- Game iteration (iterateGames): Loads and processes all active games for
  offline simulation
"""

import asyncio
import random
from datetime import date
import logging

from functions import (
    saveGameAsync, loadGameAsync, loadGames, updateAge, handleDeath,
    getPeakEnergy, updateDeathChance, get_dailyPlan, get_season,
    getIntradayActivity, parseLocations, parseOneTimeEvents, updateBio,
    messageFunction, handleUpdates, handleFinances, handleMoods,
    handleEducation, handleJob, handleRelationships, handleHabitChanges,
    set_avatar, sendRandomCharacterMessage, checkTutorialEvents,
    checkDayEvents, checkDilemmas
)
from server.websocket_messaging import (
    BatchedUpdate, sendEventMessage, sendUserInfo, sendDict
)
from event_registry import get_applicable_events
from config import config

logger = logging.getLogger(__name__)


async def iterateGames():
    """
    Iterate through all saved games and process one tick for disconnected players.

    This function loads all games from the database and runs offline simulation
    for players who are disconnected. Used for background game progression.
    """
    # Get the global playerRecords instance
    import app
    playerRecords = app.playerRecords

    games = loadGames()
    # loop through games
    if (games and len(games) > 0):
        for game in games:
            #load game
            cached_player = playerRecords.get(game[0])
            if (cached_player is None or cached_player.connection == 'disconnected'):
                foundGame = await loadGameAsync(game[0])
                #check if player is alive
                if (foundGame.c.status == "alive"):
                    print('iterating game ' + foundGame.c.firstname + ' ' + foundGame.c.lastname + ' ' + str(foundGame.minuteOfHour) + 'min' )
                    await initLifeSim(False, foundGame)


async def initLifeSim(websocket, oneTimePlayer=False):
    """
    Main game simulation loop - processes one game tick.

    This function handles all game logic updates including:
    - Time progression (minutes → hours → days)
    - Character stat updates (energy, hunger, mood, money)
    - Event checking and triggering
    - Daily/weekly/hourly tick processing
    - Location and activity updates
    - Client synchronization

    Args:
        websocket: The WebSocket connection (or False for offline processing)
        oneTimePlayer: Optional player object for one-time offline updates

    Returns:
        False to indicate completion
    """
    # Import locally to avoid circular imports
    from event_handlers import call_event_handler

    # Get the global playerRecords instance
    import app
    playerRecords = app.playerRecords

    # this runs each loop or during a one-time update
    if (websocket and websocket.userID == 'DUMMY_USER_ID'):
        return False

    player = False
    if (oneTimePlayer):
        player = oneTimePlayer
    else:
        player = playerRecords.get(websocket.userID)

    player.ticks += 1
    if player.ticks % player.gameSpeed == 0 or oneTimePlayer:
        # Place your game logic update here
        # pass #higher game speed number = slower game)
        if (player.controller == 'active' and player.status != "creating") or oneTimePlayer:
            if (player.c.status == "dead"):
                handleDeath(player)
                await saveGameAsync(player)
                return False
            else:

                player.minuteOfHour += 1
                player.time = str(player.hourOfDay) + ":" + str(player.minuteOfHour)
                if (player.minuteOfHour == 60):
                    player.minuteOfHour = 0
                if (player.minuteOfHour == 0): #hourly Ticks
                    # Create batched update
                    batch = BatchedUpdate()
                    batch.add('date', player.date)
                    batch.add('hourOfDay', player.hourOfDay)
                    batch.add('minuteOfHour', player.minuteOfHour)
                    batch.add('weekDayText', player.weekDayText)
                    batch.add('energy', player.c.energy)
                    batch.add('calcEnergy', player.c.calcEnergy)
                    batch.add('money', player.c.money)
                    batch.add('diamonds', player.c.diamonds)
                    batch.add('prestige', player.c.prestige)
                    batch.add('stress', player.c.stress)
                    batch.add('happiness', player.c.happiness)
                    batch.add('occupation', player.c.occupation)
                    batch.add('location', player.c.location)
                    batch.add('schedules', player.c.schedules)
                    batch.add('intraDayMessage', player.c.intraDayMessage)
                    batch.add('dailyPlan', player.c.dailyPlan)
                    batch.add('gameSpeed', player.gameSpeed)

                    # This will be sent at end of tick (line 423-424)
                    updateObject = batch.to_dict()
                    player.message = False
                    player.hourOfDay += 1
                    result = updateAge(player)
                    if (result.type == 'messageEvent'):
                        await sendEventMessage(websocket, result)
                    # update date
                    if (player.hourOfDay == 24): # daily ticks
                        player.hourOfDay = 0
                        if (player.dayOfYear == 365):
                            player.dayOfYear = 1
                        else:
                            player.dayOfYear += 1
                        if (player.dayOfWeek == 7):
                            player.dayOfWeek = 1
                        else:
                            player.dayOfWeek += 1
                        player.date = date.fromordinal(date(2022, 1, 1).toordinal() + player.dayOfYear - 1).strftime('%m-%d')
                        player.monthOfYear = int(player.date.split('-')[0])
                        player.season = get_season(player.monthOfYear)
                        player.date = str(player.date)
                        player.time = str(player.hourOfDay) + ":00"
                        player.weekDayText = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][player.dayOfWeek-1]
                        player.daysUntilSchoolEnds = (152 - player.dayOfYear)
                        if (player.dayOfYear > 244):
                            player.daysUntilSchoolEnds = (365-244) + player.daysUntilSchoolEnds
                        player.daysSinceSchoolStarted = (player.dayOfYear - 244)
                        if (player.dayOfYear < 244):
                            player.daysSinceSchoolStarted = (121 + player.dayOfYear)
                        if (player.dayOfYear > 152 and player.dayOfYear < 244):
                            player.summerVacation = True
                        else:
                            player.summerVacation = False
                        await sendRandomCharacterMessage(player)

                        # Update messaging mood states daily based on character stress/happiness
                        from messaging_style import set_mood_state
                        for character in player.r:
                            if character.stress > 70:
                                set_mood_state(character, player, 'stressed')
                            elif character.happiness > 80:
                                set_mood_state(character, player, 'great')
                            elif character.happiness < 30:
                                set_mood_state(character, player, 'bad')
                            else:
                                set_mood_state(character, player, 'neutral')

                    # weekly ticks
                    if (player.dayOfWeek == 1 and player.hourOfDay == 0):
                        for i in range(0, len(player.r)):
                            handleFinances(player.r[i])
                            handleMoods(player, player.r[i])
                            handleEducation(player.r[i])
                            handleJob(player, player.r[i])
                            handleRelationships(player, player.r[i])
                            player.r[i].image = set_avatar(player.r[i])
                        handleFinances(player.c)
                        handleMoods(player, player.c)
                        handleRelationships(player, player.c)
                        handleJob(player, player.c)
                        handleEducation(player.c)
                        handleHabitChanges(player, player.c)
                        player.c.image = set_avatar(player.c)

                        # Weekly decay of messaging modifiers toward neutral
                        from messaging_style import decay_messaging_modifiers
                        for rel in player.relData:
                            decay_messaging_modifiers(rel)

                        await saveGameAsync(player)
                        print('weekly tick')
                        print(player.messageLog)
                        await sendUserInfo(player, websocket)
                    # check for daily events
                    if (player.hourOfDay == 0):
                        player.dayEvent = False
                        getPeakEnergy(player.c)
                        player.c.energy += 1 if player.c.energy < (100) else 0
                        if (player.c.ageDays == 1 and player.c.firstname):
                            player.message = player.c.firstname.capitalize() + " is starting their life, full of opportunities."
                            await sendUserInfo(player, websocket)
                        if player.c.ageDays > 0 and player.c.ageDays % 365 == 0:
                            player.c.ageYears += 1
                            player.c.deathChance = updateDeathChance(player.c)
                            player.message = player.c.firstname.capitalize() + " is " + str(player.c.ageYears) + " years old."
                            await saveGameAsync(player)
                            await sendUserInfo(player, websocket)
                        if player.c.deathChance*100 > (random.random()*100) or player.c.ageYears > 120:
                            player.c.status = "dead"
                            player.message = player.c.firstname.capitalize() + " has died at the age of " + str(player.c.ageYears) + " years."
                            await sendUserInfo(player, websocket)
                        if result := checkDayEvents(player, 'check'):
                            if (result.type == 'messageEvent'):
                                player.events.add(result.id)
                                await sendEventMessage(websocket, result)
                            elif (result.type == 'questionEvent'):
                                await sendEventMessage(websocket, result)
                        # Daily ticks
                        player.c = get_dailyPlan(player, player.c)
                        if (player.gameSpeed > 10):
                            updateObject['dailyPlan'] = player.c.dailyPlan
                        #get plan for each relationship
                        for i in range(0, len(player.r)):
                            if (player.r[i].status == "alive" and player.r[i].familiarity > 0):
                                player.r[i].familiarity = player.r[i].familiarity - 3 # familiarity decreases over time
                            player.r[i] = get_dailyPlan(player, player.r[i])

                    # Hourly Ticks
                    player.c = getIntradayActivity(player, player.c)
                    for i in range(0, len(player.r)):
                        player.r[i] = getIntradayActivity(player, player.r[i])
                    player = parseLocations(player)
                    parseOneTimeEvents(player)
                    player = updateBio(player)
                    if player.messageQueue and len(player.messageQueue) > 0:
                        #message is the first message in the queue, but it's not removed from the queue
                        player.message = player.messageQueue.pop(0)
                        player.messageLog.append(player.message)
                        await sendEventMessage(websocket, messageFunction('queue', player.message, player, True))
                        player.message = ""
                    if player.updateClient:
                        await sendUserInfo(player, websocket)
                        player.updateClient = False
                    if (player.gameSpeed < config.SPEED_QUESTION_PAUSE):
                        # Check tutorial events first (take priority for new players)
                        if result := checkTutorialEvents(player, 'check'):
                            if (result.type == 'messageEvent'):
                                player.events.add(result.id)
                                await sendEventMessage(websocket, result)
                            elif (result.type == 'questionEvent'):
                                await sendEventMessage(websocket, result)
                        # Then check regular events (using event registry for O(1) filtering)
                        else:
                            applicable_events = get_applicable_events(player)
                            for event_info in applicable_events:
                                try:
                                    result = event_info['func'](player, 'check')
                                    if result:
                                        if result.type == 'messageEvent':
                                            player.events.add(result.id)
                                            await sendEventMessage(websocket, result)
                                        elif result.type == 'questionEvent':
                                            await sendEventMessage(websocket, result)
                                        break  # Only trigger one event per tick
                                except Exception as e:
                                    logger.error(f"Error in event {event_info['id']}: {e}")
                    if result := checkDilemmas(player):
                        await sendEventMessage(websocket, result)
                    updateObject = handleUpdates(updateObject, player, websocket)
                    if (updateObject and updateObject != {}):
                        await sendDict(websocket, updateObject)
                elif (player.gameSpeed > 10):
                    await sendDict(websocket, {'type':'u', 'minuteOfHour': player.minuteOfHour})
                if (oneTimePlayer):
                    player.connection = 'disconnected'
                    player.controller = 'inactive'
                    player.offlineStats.minutesOffline += 1
                    print("offline for " + str(player.offlineStats.minutesOffline) + " minutes, saving game")
                    await saveGameAsync(player)
            return False
