a
    w^i>                     @   s   d Z ddlZddlmZ ddlZddlZddlZddlZddlZddl	m	Z	 ddl
mZ eeZG dd dejZdd	 Zd
d Zdd Zdd Zdd ZedddZdd Zdd Zdd Zdd Zdd ZdS )a  
Database Operations Module

This module contains all database-related functions for the BaoLife game.
Extracted from functions.py to provide a centralized database layer.

Functions:
- connect_to_database(): Create database connection
- get_database_connection(): Get or reconnect to database
- insertGame(player): Insert/replace game save
- saveGameAsync(player): Save game state (async version)
- loadGameAsync(user_id): Load game state (async version)
- saveGame(player): Save game state (legacy sync version)
- loadGame(id): Load game state (sync version)
- loadGames(): Load all game IDs
- saveConversationMessage(...): Save conversation message
- markConversationAsRead(...): Mark conversation as read
    N)Error)config)ComplexHandlerc                       s>   e Zd ZdZdddddddd	d
ddddZ fddZ  ZS )RefactoringUnpicklera  
    Custom unpickler to handle classes that were moved during refactoring.

    During the refactoring, classes were moved from functions.py to organized
    submodules. This unpickler redirects pickle to find classes in their new
    locations.

    Class migrations:
    - functions.playerClass -> core.models.playerClass
    - functions.personClass -> core.models.personClass
    - functions.locationClass -> core.models.locationClass
    - functions.ActivityRecord -> core.models.ActivityRecord
    - functions.EducationRecord -> core.models.EducationRecord
    - functions.dailyEvent -> core.models.dailyEvent
    - functions.oneTimeEvent -> core.models.oneTimeEvent
    - functions.scheduleDays -> core.models.scheduleDays
    - functions.scheduler -> core.models.scheduler
    - functions.relationshipClass -> core.models.relationshipClass
    - functions.healthCondition -> health.health_manager.HealthCondition
    - functions.HealthCondition -> health.health_manager.HealthCondition
    )core.modelsplayerClass)r   personClass)r   locationClass)r   ActivityRecord)r   EducationRecord)r   
dailyEvent)r   oneTimeEvent)r   scheduleDays)r   	scheduler)r   relationshipClass)zhealth.health_managerHealthCondition))	functionsr   )r   r   )r   r	   )r   r
   )r   r   )r   r   )r   r   )r   r   )r   r   )r   r   )r   healthCondition)r   r   c              
      sV   | j ||f}|rH|\}}td| d| d| d|  || }}t ||S )a  
        Override find_class to redirect old class locations to new ones.

        Args:
            module: Original module name from pickle
            name: Original class name from pickle

        Returns:
            The class object from the new location
        zRedirecting pickle: .z -> )CLASS_RENAMESgetloggerdebugsuper
find_class)selfmodulenameZrenamedZ
new_modulenew_name	__class__ 7/var/www/lichun.app/lichun/ws/database/db_operations.pyr   P   s    "
zRefactoringUnpickler.find_class)__name__
__module____qualname____doc__r   r   __classcell__r!   r!   r   r"   r   )   s   r   c           	      C   s  t t|  }t|drLt|jtrLt	dt
|j d t|j|_t|drt|jtrt	dt
|j d t|j|_ddlm}m}m} t|d	r|jrt|jd
st	d | |j_| |j_t|dr.d}|jD ]&}t|d
s| |_| |_|d7 }q|dkr.t	d| d t|drd}|jD ]"}t|dsD| |_|d7 }qD|dkrt	d| d |S )z
    Load pickled data with refactoring compatibility.

    Args:
        data: Pickled byte data

    Returns:
        Unpickled object with class references updated to new locations
    eventsz*Migrating player.events from list to set (z events)askedQuestionsz2Migrating player.askedQuestions from list to set (z questions)r   )initialize_messaging_traitsinitialize_messaging_patterns!initialize_relationship_modifierscmessaging_traitsz4Migrating player character - adding messaging traitsr   z	Migrated z( NPC characters - added messaging traitsrelDatamessaging_modifiersz* relationships - added messaging modifiers)r   ioBytesIOloadhasattr
isinstancer(   listr   infolensetr)   Zmessaging_styler*   r+   r,   r-   r.   Zmessaging_patternsr/   r1   r2   )	dataplayerr*   r+   r,   Zmigrated_count	characterZmigrated_rel_countrelr!   r!   r"   pickle_loads_compatf   s>    









r@   c                  C   s,   t jt jt jt jt jd} tjjf i | S )z
    Connect to database using configuration from environment variables.

    NOTE: This uses mysql.connector (sync), which does NOT support pool_timeout.
    For async connections with pool_timeout, use database_async.initialize_pool() instead.
    )hostportuserpasswordZdatabase)	r   DB_HOSTDB_PORTDB_USERDB_PASSWORDDB_NAMEmysqlZ	connectorconnect)Zconn_paramsr!   r!   r"   connect_to_database   s    
	rL   c               
   C   sD   zt  W S  ty> }  ztd|    W Y d} ~ n
d} ~ 0 0 dS )a  
    Create a new database connection for synchronous operations.

    IMPORTANT: Caller is responsible for closing the connection.
    Use with try/finally or context manager pattern.

    DEPRECATED: Use async pool (database_async.py) for new code.
    This creates a new connection on each call to avoid module-level connection issues.

    Returns:
        mysql.connector.connection: New database connection
    zFailed to connect to database: N)rL   r   r   error)er!   r!   r"   get_database_connection   s
    rO   c              	   C   s   d}d}z|t  }| }t| }d}| j| jj| jj| jj| jj	t
j| jtd|f}||| |  W |rx|  |r|  n|r|  |r|  0 dS )z[
    Insert/replace game save in database.

    DEPRECATED: Use saveGameAsync instead.
    NzREPLACE INTO lifesim_savegames (id, firstname, lastname, ageDays, ageYears, json, pickle_data) VALUES (%s, %s, %s, %s, %s, %s, %s))default)rO   cursorpickledumpsidr-   	firstnamelastnameageDaysageYearsjson__dict__r   executecommitclose)r=   mydbmycursorZserialized_playersqlvalr!   r!   r"   
insertGame   s$    
0

rb   c              
      sD  ddl m} ddl}z| j|| t| jdr6| jjndt| jdrL| jjndt| jdrb| jj	ndt| jdrx| jj
ndt| jd	r| jjnd
d}d}|||d |d |d |d |d |d fI dH  td| j d| jj d| jj d W dS  ty> } z*tjd| j d| dd W Y d}~dS d}~0 0 dS )z
    Save game state to database (async version).

    Args:
        player: playerClass instance

    Returns:
        bool: True if successful
    r   )execute_queryNrU    rV   rX   rW   statusalive)rT   pickle_datarU   rV   rX   rW   re   a  
            INSERT INTO lifesim_savegames (id, pickle_data, firstname, lastname, ageYears, ageDays, json)
            VALUES (%s, %s, %s, %s, %s, %s, '{}') AS new_data
            ON DUPLICATE KEY UPDATE
                pickle_data = new_data.pickle_data,
                firstname = new_data.firstname,
                lastname = new_data.lastname,
                ageYears = new_data.ageYears,
                ageDays = new_data.ageDays,
                lastUpdated = CURRENT_TIMESTAMP
        rT   rg   zGame saved:  ( )TzFailed to save game for : exc_infoF)database_asyncrc   rR   rT   rS   r6   r-   rU   rV   rX   rW   re   r   r   	ExceptionrM   )r=   rc   rR   player_dataqueryrN   r!   r!   r"   saveGameAsync   s6    

(rr   )user_idc              
      s   ddl m} ddl}zh|d| fI dH }|du rFtd|   W dS t|d }td|  d|jj d|jj d	 |W S  t	y } z(tj
d
|  d| dd W Y d}~dS d}~0 0 dS )z
    Load game state from database (async version).

    Args:
        user_id: User ID to load

    Returns:
        playerClass instance or None if not found
    r   )	fetch_oneN7SELECT pickle_data FROM lifesim_savegames WHERE id = %szNo saved game found for zGame loaded: rh   ri   rj   zFailed to load game for rk   Trl   )rn   rt   rR   r   r   r@   r-   rU   rV   ro   rM   )rs   rt   rR   resultr=   rN   r!   r!   r"   loadGameAsync  s     

&rw   c              
   C   s  ddl }d}d}zLzt }| }| j|| t| jdrD| jjndt| jdrZ| jjndt| jdrp| jj	ndt| jdr| jj
ndd	}d
}|||d |d |d |d |d |d f |  W W |r|  |r|  dS  ty> } z<td|  W Y d}~W |r$|  |r2|  dS d}~0 0 W |rP|  |r~|  n|rn|  |r||  0 dS )z
    Save game state to database (legacy sync version).

    DEPRECATED: Use saveGameAsync instead.
    This version uses synchronous mysql.connector and will be removed.
    r   NrU   rd   rV   rX   re   rf   )rT   rg   rU   rV   agere   a  
            INSERT INTO lifesim_savegames (id, pickle_data, firstname, lastname, age, status, lastUpdate)
            VALUES (%s, %s, %s, %s, %s, %s, NOW()) AS new_save
            ON DUPLICATE KEY UPDATE
                pickle_data = new_save.pickle_data,
                firstname = new_save.firstname,
                lastname = new_save.lastname,
                age = new_save.age,
                status = new_save.status,
                lastUpdate = NOW()
        rT   rg   rx   TzFailed to save game: F)rR   rO   rQ   rT   rS   r6   r-   rU   rV   rX   re   r[   r\   r]   ro   r   rM   )r=   rR   r^   r_   rp   rq   rN   r!   r!   r"   saveGame<  sZ    		
ry   c              
   C   s<  d}d}zzt  }| }d}||| f | }|r~|d r~|d d r~t|d d }|W W |rp|  |r||  S W W |r|  |r|  dS W nX t y } z>td|  d|  W Y d}~W |r|  |r|  dS d}~0 0 W |r
|  |r8|  n|r(|  |r6|  0 dS )zs
    Load game using parameterized query to prevent SQL injection.

    DEPRECATED: Use loadGameAsync instead.
    Nru   r   FzError loading game rk   )	rO   rQ   r[   fetchallr@   r]   ro   r   rM   )rT   r^   r_   r`   myresultr=   rN   r!   r!   r"   loadGamev  sL    
r|   c               
   C   s  d} d}zzlt  } |  }d}|| | }|rT|W W |rF|  | rR|   S W W |rd|  | rp|   dS W nP ty } z8td|  W Y d}~W |r|  | r|   dS d}~0 0 W |r|  | r|   n|r|  |  r|   0 dS )zM
    Load all game IDs.

    DEPRECATED: Create async version if needed.
    Nz SELECT id FROM lifesim_savegamesFzError loading games: )rO   rQ   r[   rz   r]   ro   r   rM   )r^   r_   stringr{   rN   r!   r!   r"   	loadGames  sJ    

r~   c           
      C   s   d}zrt  }| D}t j}d}|||| ||jf}	|||	 |  W d   n1 s^0    Y  W |r|  n|r|  0 dS )zg
    Save a conversation message to the database.

    DEPRECATED: Create async version if needed.
    NzaINSERT INTO messages (id, partner, player, message, sender, date) VALUES (%s, %s, %s, %s, %s, %s))	rO   rQ   uuiduuid4hexdater[   r\   r]   )
messager>   ZplayerIDsenderr=   r^   r_   rT   r`   ra   r!   r!   r"   saveConversationMessage  s    

(
r   c                 C   s6   t dt| jD ] }| j| j|krd| j| _qdS )Nr   FT)ranger:   conversationsr>   unread)r=   characterIDir!   r!   r"   markConversationAsRead  s    r   )r&   Zmysql.connectorrJ   r   rR   rY   r   loggingr3   r   server.websocket_messagingr   	getLoggerr#   r   Z	Unpicklerr   r@   rL   rO   rb   rr   strrw   ry   r|   r~   r   r   r!   r!   r!   r"   <module>   s,   
=7;#: