#!/usr/bin/env python
"""
Utility Helper Functions for BaoLife Game

This module contains general-purpose utility functions extracted from functions.py
to improve code organization and maintainability.

Functions:
    - Array manipulation: getFromArray, randArray
    - Search utilities: find_by_id, find_where, find, find_where_test, findCharacters
    - Random number generation: rand
    - String manipulation: remove_text_before_first_colon, ordinal_suffix
    - Date/time utilities: getWeekDay, upcoming_saturday, generate_random_date, get_season
    - Location utilities: parseLocations
    - Game utilities: getOppositeSex
    - JWT/Authentication: create_jwt_token
    - Pickle compatibility: _unpickle_health_condition, _reduce_health_condition
"""

import random
import datetime
import time
import jwt


# ============================================================================
# Array and Collection Utilities
# ============================================================================

def getFromArray(array, type, amount=0, value=0):
    """
    Filter array by object type attribute.

    Args:
        array: List of objects with 'type' attribute
        type: Value to match against object.type
        amount: If 0, return all matches; if 1, return first match
        value: Unused parameter (kept for compatibility)

    Returns:
        List of matching objects if amount=0, single object if amount=1, False if none found
    """
    result = []
    for i in range(0, len(array)):
        if (array[i].type == type):
            result.append(array[i])
    if (amount == 0):
        return result
    else:
        if (len(result) > 0):
            return result[0]
        else:
            return False


def randArray(array):
    """
    Return a random element from an array.

    Args:
        array: List to select from

    Returns:
        Random element from the array
    """
    return array[random.randint(0, len(array) - 1)]


# ============================================================================
# Search and Filter Utilities
# ============================================================================

def find_by_id(iterable, id):
    """
    Find an object in iterable by its id attribute.

    Args:
        iterable: Collection of objects with 'id' attribute
        id: ID value to search for

    Returns:
        First matching object or None if not found
    """
    for item in iterable:
        if item.id == id:
            return item
    return None


def find_where(iterable, dct):
    """
    Find first object where all key-value pairs match.

    Args:
        iterable: Collection of objects to search
        dct: Dictionary of attribute names and values to match

    Returns:
        First matching object or None if not found
    """
    for item in iterable:
        if all(getattr(item, key, None) == value for key, value in dct.items()):
            return item
    return None


def findCharacters(player, check):
    """
    Filter player relationships based on a check condition.

    Args:
        player: Player object with 'r' (relationships) attribute
        check: Boolean condition to filter by

    Returns:
        List of matching person objects
    """
    results = []
    for person in player.r:
        if (check):
            results.append(person)
    return results


def find(iterable, dct):
    """
    Generator that yields objects where all key-value pairs match.
    Converts objects to dictionaries for comparison.

    Args:
        iterable: Collection of objects to search
        dct: Dictionary of attribute names and values to match

    Yields:
        Dictionary representation of matching objects
    """
    for item in iterable:
        item = item.__dict__
        if all(item[key] == value for key, value in dct.items()):
            yield item


def find_where_test(iterable, dct):
    """
    Generator that yields objects with advanced comparison operators.
    Supports __gt (greater than) and __lt (less than) suffixes in keys.

    Args:
        iterable: Collection of objects to search
        dct: Dictionary with keys like 'age__gt': 23 or 'score__lt': 100

    Yields:
        Matching objects

    Example:
        find_where_test(my_list, {'age__gt': 23, 'name': 'John'})
    """
    for item in iterable:
        item_dict = item.__dict__
        match = True
        for key, value in dct.items():
            if "__gt" in key:
                attr_key = key.replace("__gt", "")
                if item_dict.get(attr_key, float("-inf")) <= value:
                    match = False
                    break
            elif "__lt" in key:
                attr_key = key.replace("__lt", "")
                if item_dict.get(attr_key, float("inf")) >= value:
                    match = False
                    break
            elif item_dict.get(key) != value:
                match = False
                break
        if match:
            yield item


# ============================================================================
# Random Number Utilities
# ============================================================================

def rand(num1, num2):
    """
    Generate random integer between num1 and num2 (inclusive).

    Args:
        num1: Minimum value
        num2: Maximum value

    Returns:
        Random integer in range [num1, num2]
    """
    return random.randint(num1, num2)


# ============================================================================
# String Manipulation Utilities
# ============================================================================

def remove_text_before_first_colon(message):
    """
    Remove text before the first colon in a message.

    Args:
        message: String that may contain a colon

    Returns:
        Text after first colon (stripped), or original message if no colon
    """
    parts = message.split(":", 1)
    if len(parts) > 1:
        return parts[1].strip()
    return parts[0]


def ordinal_suffix(num):
    """
    Add ordinal suffix to a number (1st, 2nd, 3rd, 4th, etc.).

    Args:
        num: Integer to add suffix to

    Returns:
        String with number and ordinal suffix (e.g., "1st", "22nd")
    """
    if 10 < num % 100 < 20:
        return str(num) + "th"
    else:
        suffixes = {1: "st", 2: "nd", 3: "rd"}
        return str(num) + suffixes.get(num % 10, "th")


# ============================================================================
# Date and Time Utilities
# ============================================================================

def getWeekDay(player):
    """
    Get day of week based on player's age in days.

    Args:
        player: Player object with c.ageDays attribute

    Returns:
        String name of weekday (Sunday through Saturday)
    """
    day = player.c.ageDays % 7
    if (day == 0):
        return "Sunday"
    elif (day == 1):
        return "Monday"
    elif (day == 2):
        return "Tuesday"
    elif (day == 3):
        return "Wednesday"
    elif (day == 4):
        return "Thursday"
    elif (day == 5):
        return "Friday"
    elif (day == 6):
        return "Saturday"


def upcoming_saturday(date_string):
    """
    Calculate the next Saturday from a given date string.

    Args:
        date_string: Date in format "MM-DD"

    Returns:
        datetime.date object of the upcoming Saturday
    """
    # Parse the date string
    month, day = map(int, date_string.split("-"))
    year = datetime.datetime.now().year

    # Get the given date
    given_date = datetime.datetime(year, month, day)

    # Calculate the number of days until Saturday
    days_until_saturday = (5 - given_date.weekday() + 7) % 7

    # Return the upcoming Saturday
    return (given_date + datetime.timedelta(days=days_until_saturday)).date()


def get_season(month):
    """
    Get season name based on month number.

    Args:
        month: Integer month (1-12)

    Returns:
        String season name (Spring, Summer, Autumn, Winter)
    """
    if 3 <= month <= 5:
        return "Spring"
    elif 6 <= month <= 8:
        return "Summer"
    elif 9 <= month <= 11:
        return "Autumn"
    else:  # months 12, 1, 2
        return "Winter"


def generate_random_date():
    """
    Generate a random date in MM-DD format.
    Accounts for different month lengths.

    Returns:
        String in format "MM-DD" (e.g., "03-15")
    """
    month = random.randint(1, 12)
    if month in [4, 6, 9, 11]:
        day = random.randint(1, 30)
    elif month == 2:
        day = random.randint(1, 28)  # Assuming non-leap year for simplicity
    else:
        day = random.randint(1, 31)
    return f"{month:02d}-{day:02d}"


# ============================================================================
# Location Utilities
# ============================================================================

def parseLocations(player):
    """
    Update location objects with current people present.
    Increases familiarity between characters at same location.

    Args:
        player: Player object with c (character), r (relationships), l (locations)

    Returns:
        Updated player object
    """
    # Go through all player.r and add id to player.l.people
    for l in player.l:
        l.people = []
        playerPresent = False
        if (player.c.location == l.id):
            l.people.append(player.c.id)
            playerPresent = True
        for r in player.r:
            if r.location == l.id:
                if (playerPresent):
                    r.familiarity = r.familiarity + 10
                l.people.append(r.id)
    return player


# ============================================================================
# Game Utilities
# ============================================================================

def getOppositeSex(sex):
    """
    Get the opposite sex/gender.

    Args:
        sex: String "Male" or "Female"

    Returns:
        String opposite sex, or None if input invalid
    """
    if (sex == "Male"):
        return "Female"
    if (sex == "Female"):
        return "Male"


# ============================================================================
# JWT Token Utilities
# ============================================================================

# APNS Configuration Constants
APNS_KEY_ID = "ZW35P75J3A"
APNS_AUTH_KEY = "/var/www/lichun.app/lichun/AuthKey_ZW35P75J3A.p8"
TEAM_ID = "2A7RZ5P98P"
BUNDLE_ID = "lichun.lichunWebsocket"
ALGORITHM = 'ES256'


def create_jwt_token():
    """
    Create a JWT token for Apple Push Notification Service (APNS).

    Returns:
        Encoded JWT token string
    """
    token = jwt.encode(
        payload={
            'iss': TEAM_ID,
            'iat': time.time()
        },
        key=open(APNS_AUTH_KEY, 'r').read(),
        algorithm=ALGORITHM,
        headers={
            'alg': ALGORITHM,
            'kid': APNS_KEY_ID,
        }
    )
    # Return the token directly without decoding
    return token


# ============================================================================
# Pickle Compatibility Utilities
# ============================================================================

def _unpickle_health_condition(*args):
    """
    Helper to unpickle old healthCondition objects as HealthCondition.
    Used for backward compatibility with old save games.

    Args:
        *args: Variable arguments passed from pickle

    Returns:
        HealthCondition object

    Note:
        Requires HealthCondition class to be imported where this is used
    """
    # This function needs HealthCondition class imported in the calling context
    # It's used by copyreg for pickle backward compatibility
    from health.health_manager import HealthCondition
    return HealthCondition(*args)


def _reduce_health_condition(obj):
    """
    Register copyreg reducer for HealthCondition pickle serialization.

    Args:
        obj: HealthCondition object to reduce

    Returns:
        Tuple of (unpickler function, arguments tuple)
    """
    return (_unpickle_health_condition, (obj.id, obj.title, obj.healthModifier,
                                          obj.averageDuration, obj.description, obj.image))
