#!/usr/bin/env python
"""
Stats Manager Module

This module contains all stats and state update functions extracted from functions.py.
It manages player statistics, character updates, event checking, and various game state modifications.

Functions included:
- connect(player): Handle player reconnection and offline stats
- checkDilemmas(player): Check and trigger active dilemmas
- checkEvents(player, type): Check and trigger regular events
- checkDayEvents(player, type): Check and trigger day-specific events
- checkTutorialEvents(player, type): Check and trigger tutorial events
- scheduleComplete(person, scheduleID): Check if a schedule is complete
- updateAge(player): Update character ages and handle birthdays/deaths
- setLikesDislikes(person): Set random likes and dislikes for a character
- handleUpdates(updateObject, player, websocket): Handle incremental state updates
- parseOneTimeEvents(player): Parse and trigger one-time scheduled events
- handleMoods(player, person): Update character moods based on stats
- handleFinances(person): Handle character finances and savings
- getPeakEnergy(person): Calculate peak energy based on activities and habits

Note:
- FocusClass, getFocus(), getFocuses() are imported from education.education_manager
- updateDeathChance() is imported from health.health_manager
"""

import random
import logging

# Import FocusClass and related functions from education module (avoid duplication)
from education.education_manager import FocusClass, getFocuses, getFocus

# Import updateDeathChance from health module (avoid duplication)
from health.health_manager import updateDeathChance


# Main Stats/State Update Functions
def connect(player):
    """
    Handle player reconnection and display offline time message.

    Args:
        player: playerClass instance
    """
    player.connection = 'connected'
    minutes = player.offlineStats.minutesOffline

    if minutes > 0:
        if minutes < 60:
            unit = "minute"
            time = minutes
        elif minutes < 1440:  # minutes in a day
            unit = "hour"
            time = minutes // 60
        else:
            unit = "day"
            time = minutes // 1440

        # If the time is more than 1, add 's' to the unit to make it plural
        if time > 1:
            unit += 's'

        player.messageQueue.append(f"Welcome back, you missed {time} {unit} of your life.")
        player.offlineStats.minutesOffline = 0


def checkDilemmas(player):
    """
    Check active dilemmas and trigger them randomly.

    Args:
        player: playerClass instance

    Returns:
        Event result if triggered, None otherwise
    """
    for dilemma in player.activeDilemmas:
        if (random.random() * 100 < 2):
            import events
            for i in dir(events):
                if i == dilemma.function:
                    item = getattr(events, i)
                    return item(player, type='dilemma', dilemma=dilemma)
    return None


def checkEvents(player, type):
    """
    Check and trigger regular events from events.py.

    Args:
        player: playerClass instance
        type (str): Event type to check for

    Returns:
        Event result if triggered, None otherwise
    """
    import events
    import inspect

    excluded_names = {
        'questionEvent', 'messageEvent', 'timeEvent', 'dilemmaClass',
        'answerOption', 'ConversationContextManager', 'oneTimeEvent',
        'apply_tutorial_modifiers', 'get_event_type', 'parse_event_data',
        'check_tutorial_triggers'
    }
    for i in dir(events):
        item = getattr(events, i)
        # Skip if not callable, excluded name, or async function (coroutine function)
        if callable(item) and item.__name__ not in excluded_names and not inspect.iscoroutinefunction(item):
            result = item(player, type)
            if (result):
                return result
    return None


def checkDayEvents(player, type):
    """
    Check and trigger day-specific events from dayEvents.py.

    Args:
        player: playerClass instance
        type (str): Event type to check for

    Returns:
        Event result if triggered, None otherwise
    """
    import dayEvents
    import inspect

    excluded_names = {
        'questionEvent', 'messageEvent', 'timeEvent', 'get_dailyPlan',
        'dilemmaClass', 'ConversationContextManager', 'oneTimeEvent'
    }
    for i in dir(dayEvents):
        item = getattr(dayEvents, i)
        # Skip if not callable, excluded name, or async function (coroutine function)
        if callable(item) and item.__name__ not in excluded_names and not inspect.iscoroutinefunction(item):
            result = item(player, type)
            if (result):
                return result
    return None


def checkTutorialEvents(player, type):
    """
    Check and trigger tutorial events if player is in tutorial mode.

    Args:
        player: playerClass instance
        type (str): Event type to check for

    Returns:
        Event result if triggered, None otherwise
    """
    try:
        import tutorial_events
        # First check if player is in tutorial mode
        if not tutorial_events.is_tutorial_mode(player):
            return None

        # Check tutorial triggers
        triggers = tutorial_events.check_tutorial_triggers(player)
        for trigger_func in triggers:
            result = trigger_func(player, type)
            if result:
                return result
    except Exception as e:
        logging.error(f"Error checking tutorial events: {e}")
    return None


def scheduleComplete(person, scheduleID):
    """
    Check if a schedule has been completed.

    Args:
        person: personClass instance
        scheduleID (str): ID of the schedule to check

    Returns:
        bool: True if schedule is complete, False otherwise
    """
    for i in range(0, len(person.schedules)):
        if (scheduleID in person.schedules[i].id and person.schedules[i].executions == person.schedules[i].duration):
            return True
    return False


def updateAge(player):
    """
    Update character ages and handle birthdays, deaths, and relationship decay.

    Args:
        player: playerClass instance

    Returns:
        playerClass or messageEvent: Updated player or birthday message event
    """
    from events import messageFunction

    player.c.ageHours = player.c.ageHours + 1
    if (player.c.ageHours % 24 == 0):
        player.c.ageDays = player.c.ageDays + 1
        for index, item in enumerate(player.r):
            if item.status == 'alive':
                player.r[index].ageDays = item.ageDays + 1
                if (player.c.ageYears > 18 and item.ageDays % 30 == 0):
                    player.r[index].affinity -= 1  # change made
                if (item.ageDays % 365) == 0:
                    player.r[index].ageYears += 1
                    player.r[index].affinity -= 1
                    player.r[index].deathChance = updateDeathChance(item)
                    if ('classmate' not in item.relationships and getattr(item, 'title', False)):
                        message = "Your " + item.title + " " + item.firstname + " " + item.lastname + " is now " + str(item.ageYears) + " years old."
                        return messageFunction('birthday', message, player, True)
                if item.deathChance * item.health * 100 > (random.random() * 100) or item.ageYears > 120:
                    player.r[index].status = "dead"
                    player.message = "Your " + item.title + " " + item.firstname.capitalize() + " " + item.lastname.capitalize() + " has died at the age of " + str(item.ageYears) + " years old."

                if (player.r[index].affinity < -100):
                    player.r[index].affinity = -100
    return player


def setLikesDislikes(person):
    """
    Set random likes and dislikes for a character.

    Args:
        person: personClass instance

    Returns:
        personClass: Updated person with likes and dislikes
    """
    possible_interests = ['sports', 'music', 'art', 'reading', 'writing', 'science', 'math', 'history', 'politics', 'philosophy', 'religion', 'nature', 'animals', 'travel', 'food', 'cooking', 'cars', 'fashion', 'fitness', 'health', 'technology', 'video games', 'movies', 'tv', 'theater', 'dance', 'comedy', 'drama', 'horror', 'romance', 'action', 'adventure', 'fantasy', 'sci-fi', 'mystery', 'thriller', 'crime', 'documentary', 'anime', 'manga', 'cartoons', 'board games', 'card games', 'Broccoli', 'Celery', 'Mushrooms', 'Brussels sprouts', 'Onions']

    person.likes = random.sample(possible_interests, random.randint(1, 5))
    possible_interests = [interest for interest in possible_interests if interest not in person.likes]
    person.dislikes = random.sample(possible_interests, random.randint(1, 5))

    return person


def handleUpdates(updateObject, player, websocket):
    """
    Handle incremental state updates to send to client.
    Only includes changed values to minimize data transfer.

    Args:
        updateObject (dict): Object to populate with updates
        player: playerClass instance
        websocket: WebSocket connection (not currently used)

    Returns:
        dict or False: Update object with type 'u' if changes exist, False otherwise
    """
    # List of attributes to check
    attributes = [
        'energy', 'calcEnergy', 'happiness', 'stress', 'money', 'prestige',
        'occupation', 'intraDayMessage', 'location'
    ]

    # Check attributes
    for attribute in attributes:
        player_value = getattr(player.c, attribute)
        # Use .get() to safely access dictionary keys
        if player_value != updateObject.get(attribute):
            updateObject[attribute] = player_value
        else:
            updateObject.pop(attribute, None)

    # Check lists
    list_attributes = ['schedules', 'dailyPlan']
    for attribute in list_attributes:
        player_value = getattr(player.c, attribute)
        # Use .get() to safely access and provide empty list as default
        if len(player_value) != len(updateObject.get(attribute, [])):
            updateObject[attribute] = player_value
        else:
            updateObject.pop(attribute, None)

    # Check game speed and related conditions
    if player.gameSpeed != updateObject.get('gameSpeed'):
        print(player.gameSpeed)
        updateObject['gameSpeed'] = player.gameSpeed
    else:
        updateObject.pop('gameSpeed', None)

    if (player.hourOfDay != updateObject.get('hourOfDay')):  # slow mode
        updateObject.update({
            'date': player.date,
            'hourOfDay': player.hourOfDay,
            'weekDayText': player.weekDayText
        })
    elif (player.gameSpeed <= 1 and player.date != updateObject.get('date') and player.hourOfDay == 0):  # fast mode
        updateObject.update({
            'date': player.date,
            'hourOfDay': player.hourOfDay,
            'weekDayText': player.weekDayText
        })
    else:
        for key in ['date', 'hourOfDay', 'weekDayText']:
            updateObject.pop(key, None)

    # Final check and return
    if updateObject:
        updateObject['type'] = "u"
        return updateObject

    return False


def parseOneTimeEvents(player):
    """
    Parse and trigger one-time scheduled events.

    Args:
        player: playerClass instance
    """
    for i in range(0, len(player.c.oneTimeEvents)):
        event = player.c.oneTimeEvents[i]
        if (event.date == player.date and player.hourOfDay == event.hour):
            player.messageQueue.append(event.message)
            player.c.oneTimeEvents.pop(i)
            if (event.completionFunc != None):
                if hasattr(event, 'run_func'):  # Check if run_func exists before trying to call it
                    event.run_func(player)
                event.completionFunc = None
                event.completionArgs = False
                event.completionKwargs = False


def handleMoods(player, person):
    """
    Update character mood based on energy and happiness levels.

    Args:
        player: playerClass instance (not currently used)
        person: personClass instance to update mood for
    """
    # First draft, simple moods
    # self.moods = ["Calm","Stressed","Exhausted","Fulfilled","Depressed","Happy"]
    if (person.energy < 40 and person.energy > 10):
        person.mood = "Stressed"
    if (person.energy <= 10):
        person.mood = "Exhausted"
    if (person.energy >= 40):
        person.mood = "Calm"
    if (person.energy >= 40 and person.happiness >= 60):
        person.mood = "Fulfilled"
    if (person.energy >= 40 and person.happiness <= 40):
        person.mood = "Depressed"
    if (person.energy >= 40 and person.happiness >= 40 and person.happiness <= 60):
        person.mood = "Happy"


def handleFinances(person):
    """
    Handle character finances including income and savings.

    Args:
        person: personClass instance
    """
    savingRatio = 0.1
    if person.spendingHabits == 'frugal':
        savingRatio = 0.2
    elif person.spendingHabits == 'extravagant':
        savingRatio = 0.05
    income = 0
    if (person.job != False):
        for record in person.activityRecords:
            if record.type == 'job':
                income += record.level.salary
        job = getattr(person, 'job', None)
        if income:
            if getattr(job, 'hourType', None) == 'partTime':
                income *= 0.3
            person.money += income * savingRatio


def getPeakEnergy(person):
    """
    Calculate peak energy based on activities, habits, and focus settings.
    Updates person.peakEnergy and person.calcEnergy.

    Args:
        person: personClass instance
    """
    # Add up all the energy costs of activities
    peakEnergy = 0
    # if getattr(person,'children',False):
    #     peakEnergy += 10
    for habit in person.habits:
        # Quitting habits costs energy
        if habit.status == 'quitting':
            peakEnergy += 5

    for activity in person.activities:
        if (getattr(activity, 'energyModifier', False)):
            peakEnergy += activity.energyModifier
        for record in person.activityRecords:
            if (record.id == activity.id):
                if (record.type == 'job'):
                    peakEnergy += record.level.energy_modifier
                foundFocus = getFocus(record.focus)
                if foundFocus:
                    peakEnergy += foundFocus.energyModifier

    person.peakEnergy = peakEnergy
    person.calcEnergy = person.energy - person.peakEnergy
