
    Ki                         S r SSKrSSKrSSKrSSKJr  SSKJrJr  SSK	J
r
  SSKJr  \ " S S5      5       r\ " S	 S
5      5       r " S S5      rg)z SQLite database for persistence.    N)Path)	dataclassfield)Table)
TrustLevelc                       \ rS rSr% Sr\\S'   \\S'   \\S'   \\S'   Sr\\S'   S	r	\\S
'   \
R                  r\
\S'   Sr\\S'   \" \S9r\\   \S'   Srg)
UserRecord   a  User record loaded from the database.

Attributes:
    id: Internal numeric id.
    username: Username (display name).
    password_hash: Stored password hash.
    uuid: Persistent UUID for stats tracking.
    locale: Preferred locale.
    preferences_json: JSON preferences blob.
    trust_level: Trust level for permissions.
    approved: Whether the account is approved.
    fluent_languages: Languages the user knows.
idusernamepassword_hashuuidenlocale{}preferences_jsontrust_levelFapproved)default_factoryfluent_languages N)__name__
__module____qualname____firstlineno____doc__int__annotations__strr   r   r   USERr   r   boolr   listr   __static_attributes__r       :c:\Users\dbart\PlayPalace11\server\persistence\database.pyr	   r	      s_     	GM
IFC c (ooK-Hd"'"=d3i=r$   r	   c                   `    \ rS rSr% Sr\\S'   \\S'   \\S'   \\S'   \\S'   \\S'   \\S	'   S
rg)SavedTableRecord(   a*  Saved table record from the database.

Attributes:
    id: Internal numeric id.
    username: Owner username.
    save_name: User-visible save name.
    game_type: Game type identifier.
    game_json: Serialized game state.
    members_json: Serialized member list.
    saved_at: Timestamp string.
r   r   	save_name	game_type	game_jsonmembers_jsonsaved_atr   N)	r   r   r   r   r   r   r   r   r#   r   r$   r%   r'   r'   (   s-    
 	GMNNNMr$   r'   c                      \ rS rSrSrShS\\-  4S jjrSiS jrSiS jr	SiS	 jr
SiS
 jr\S\R                  S\4S j5       rSrS\S\S-  4S jrS\R(                  S4S\S\S\S\S\S\4S jjrS\S\4S jrS\S\SS4S jrS\S\SS4S jrS\S\SS4S jrS\S\S\S\SS4
S jrS\S\R                  S-  4S  jr SjS\S!\S"\S-  SS4S# jjrS\4S$ jrS\S-  4S% jr S\S\SS4S& jr!SkS'\S\"\   4S( jjr#S\"\   4S) jr$S\S\4S* jr%S\S\4S+ jr&SkS'\S\"\   4S, jjr'S\S-  4S- jr(SkS.\S\"\   4S/ jjr)S\S\"\   4S0 jr*S\S1\"\   SS4S2 jr+S\S\"\   4S3 jr,S\S4\S\4S5 jr-S\S4\S\4S6 jr.S4\S\"\   4S7 jr/S\0\\"\   4   4S8 jr1S9\2SS4S: jr3S;\S\2S-  4S< jr4S\"\2   4S= jr5S;\SS4S> jr6SiS? jr7S@\"\2   SS4SA jr8S\SB\SC\SD\SE\S\94SF jr:S\S\"\9   4SG jr;SH\S\9S-  4SI jr<SH\SS4SJ jr= SjSC\SK\SL\SM\"\>\\\\4      SN\0S-  S\4SO jjr?  SlSP\SC\S-  SQ\S\"\0   4SR jjr@SS\S\"\0   4ST jrASjSC\SQ\S-  S\"\>   4SU jjrBSC\S\04SV jrCSjSP\SC\S-  S\04SW jjrDSP\SC\S\>\E\E4   S-  4SX jrFSP\SC\SY\ESZ\ESS4
S[ jrG SmSC\SQ\S\"\>\\E\E4      4S\ jjrHSiS] jrIS^\S_\S`\Sa\S;\S-  Sb\SS4Sc jrJS\"\0   4Sd jrKS^\SS4Se jrLSiSf jrMSgrNg)nDatabase?   zcSQLite database for PlayPalace persistence.

Stores users, tables, saved tables, and game results.
db_pathc                 2    [        U5      U l        SU l        g)z,Initialize the database wrapper with a path.N)r   r1   _conn)selfr1   s     r%   __init__Database.__init__E   s    G}04
r$   returnNc                     [         R                  " [        U R                  5      5      U l        [         R                  U R                  l        U R                  R                  S5        U R                  5         g! [         R
                   a9  n[        SU R                   SU 3[        R                  S9  [        S5      UeSnAff = f)z4Connect to the database and create tables if needed.z#ERROR: Failed to open database at 'z': file   NzPRAGMA foreign_keys = ON)sqlite3connectr   r1   r3   Errorprintsysstderr
SystemExitRowrow_factoryexecute_create_tables)r4   excs     r%   r=   Database.connectJ   s    	) T\\):;DJ ")



56 }} 	)5dll^3seLZZ Q-S(	)s   .A; ;C4CCc                 j    U R                   (       a"  U R                   R                  5         SU l         gg)zClose the database connection.N)r3   close)r4   s    r%   rJ   Database.closeX   s%    ::JJDJ r$   c                 F   U R                   R                  5       nUR                  S5        UR                  S5        UR                  S5        UR                  S5        UR                  S5        UR                  S5        UR                  S5        UR                  S5        UR                  S	5        UR                  S
5        UR                  S5        UR                  S5        UR                  S5        U R                   R                  5         U R	                  5         g)z+Create database tables if they don't exist.a  
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                username TEXT UNIQUE NOT NULL,
                password_hash TEXT NOT NULL,
                uuid TEXT NOT NULL,
                locale TEXT DEFAULT 'en',
                preferences_json TEXT DEFAULT '{}',
                trust_level INTEGER DEFAULT 1,
                approved INTEGER DEFAULT 0,
                fluent_languages TEXT DEFAULT '[]'
            )
        a*  
            CREATE TABLE IF NOT EXISTS transcriber_assignments (
                user_id INTEGER NOT NULL,
                lang_code TEXT NOT NULL,
                PRIMARY KEY (user_id, lang_code),
                FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
            )
        a9  
            CREATE TABLE IF NOT EXISTS tables (
                table_id TEXT PRIMARY KEY,
                game_type TEXT NOT NULL,
                host TEXT NOT NULL,
                members_json TEXT NOT NULL,
                game_json TEXT,
                status TEXT DEFAULT 'waiting'
            )
        ay  
            CREATE TABLE IF NOT EXISTS saved_tables (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                username TEXT NOT NULL,
                save_name TEXT NOT NULL,
                game_type TEXT NOT NULL,
                game_json TEXT NOT NULL,
                members_json TEXT NOT NULL,
                saved_at TEXT NOT NULL
            )
        a  
            CREATE TABLE IF NOT EXISTS game_results (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                game_type TEXT NOT NULL,
                timestamp TEXT NOT NULL,
                duration_ticks INTEGER,
                custom_data TEXT
            )
        a  
            CREATE TABLE IF NOT EXISTS game_result_players (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                result_id INTEGER REFERENCES game_results(id) ON DELETE CASCADE,
                player_id TEXT NOT NULL,
                player_name TEXT NOT NULL,
                is_bot INTEGER NOT NULL,
                is_virtual_bot INTEGER NOT NULL DEFAULT 0
            )
        zm
            CREATE INDEX IF NOT EXISTS idx_game_results_type
            ON game_results(game_type)
        zr
            CREATE INDEX IF NOT EXISTS idx_game_results_timestamp
            ON game_results(timestamp)
        zx
            CREATE INDEX IF NOT EXISTS idx_result_players_player
            ON game_result_players(player_id)
        a  
            CREATE TABLE IF NOT EXISTS player_ratings (
                player_id TEXT NOT NULL,
                game_type TEXT NOT NULL,
                mu REAL NOT NULL,
                sigma REAL NOT NULL,
                PRIMARY KEY (player_id, game_type)
            )
        ax  
            CREATE TABLE IF NOT EXISTS refresh_tokens (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                username TEXT NOT NULL,
                token TEXT UNIQUE NOT NULL,
                expires_at INTEGER NOT NULL,
                created_at INTEGER NOT NULL,
                revoked_at INTEGER,
                replaced_by TEXT
            )
        zt
            CREATE INDEX IF NOT EXISTS idx_refresh_tokens_username
            ON refresh_tokens(username)
        zu
            CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires
            ON refresh_tokens(expires_at)
        N)r3   cursorrE   commit_run_migrationsr4   rM   s     r%   rF   Database._create_tables^   s+   ""$ 	  	 	  	 	 	 		 	 
 
	 	  	 	 	 		 	  	 	  	 	  	 	  	 	 
 
	 	  	 	  	
 	

 	r$   c                 $   U R                   R                  5       nUR                  S5        UR                  5        Vs/ s H  o"S   PM	     nnSU;  a+  UR                  S5        U R                   R	                  5         SU;  a<  UR                  S5        UR                  S5        U R                   R	                  5         UR                  S5        UR                  5        Vs/ s H  o"S   PM	     nnS	U;  a+  UR                  S
5        U R                   R	                  5          UR                  S5        U R                   R	                  5         SU;  a+  UR                  S5        U R                   R	                  5         UR                  S5        UR                  5       (       d+  UR                  S5        U R                   R	                  5         UR                  S5        UR                  5       (       dN  UR                  S5        UR                  S5        UR                  S5        U R                   R	                  5         ggs  snf s  snf ! [
        R                   a)  n[        S[        R                  S9  [        S5      UeSnAff = f)z/Run database migrations for existing databases.zPRAGMA table_info(users)r;   r   z:ALTER TABLE users ADD COLUMN trust_level INTEGER DEFAULT 1r   z7ALTER TABLE users ADD COLUMN approved INTEGER DEFAULT 0zUPDATE users SET approved = 1z&PRAGMA table_info(game_result_players)is_virtual_botzKALTER TABLE game_result_players ADD COLUMN is_virtual_bot INTEGER DEFAULT 0zTCREATE UNIQUE INDEX IF NOT EXISTS idx_users_username_lower ON users(lower(username))zzERROR: Duplicate usernames exist when compared case-insensitively. Please resolve duplicates before restarting the server.r9   Nr   z?ALTER TABLE users ADD COLUMN fluent_languages TEXT DEFAULT '[]'zTSELECT name FROM sqlite_master WHERE type='table' AND name='transcriber_assignments'aF  
                CREATE TABLE IF NOT EXISTS transcriber_assignments (
                    user_id INTEGER NOT NULL,
                    lang_code TEXT NOT NULL,
                    PRIMARY KEY (user_id, lang_code),
                    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
                )
            zKSELECT name FROM sqlite_master WHERE type='table' AND name='refresh_tokens'a  
                CREATE TABLE IF NOT EXISTS refresh_tokens (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT NOT NULL,
                    token TEXT UNIQUE NOT NULL,
                    expires_at INTEGER NOT NULL,
                    created_at INTEGER NOT NULL,
                    revoked_at INTEGER,
                    replaced_by TEXT
                )
            z
                CREATE INDEX IF NOT EXISTS idx_refresh_tokens_username
                ON refresh_tokens(username)
            z
                CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires
                ON refresh_tokens(expires_at)
            )r3   rM   rE   fetchallrN   r<   IntegrityErrorr?   r@   rA   rB   fetchone)r4   rM   rowcolumnsgrp_columnsrG   s         r%   rO   Database._run_migrations   s4   ""$ 	12%+__%67%6cq6%67'NNWXJJW$NNTUNN:;JJ 	?@)/):;):#1v):;;.NN] JJ	)NNf JJ W,NN\]JJ 	b	
   NN   JJ 	Y	
   NN 
 
 NN   NN   JJ) !u 8 < %% 	)JZZ
 Q-S(	)s#   I$I&+I J&$J

JrW   c                     U S   b  U S   OSn[        U S   U S   U S   U S   U S   =(       d    SU S	   =(       d    S
[        U5      U S   b  [        U S   5      OS[        R                  " U S   =(       d    S5      S9	$ )z'Build a UserRecord from a database row.r   r;   r   r   r   r   r   r   r   r   r   Fr   [])	r   r   r   r   r   r   r   r   r   )r	   r   r!   jsonloads)rW   trust_level_ints     r%   _user_from_rowDatabase._user_from_row5  s     14M0B0N#m,TU4y_o.Vx=(D !34<"?3.1*o.IT#j/*u!ZZ,>(?(G4H

 
	
r$   zdid, username, password_hash, uuid, locale, preferences_json, trust_level, approved, fluent_languagesr   c                     U R                   R                  5       nUR                  SU R                   S3U45        UR	                  5       nU(       a  U R                  U5      $ g)zGet a user by username.SELECT z, FROM users WHERE lower(username) = lower(?)N)r3   rM   rE   _USER_COLUMNSrV   r`   r4   r   rM   rW   s       r%   get_userDatabase.get_userG  s^    ""$d(())UVK	
 oo&&s++r$   r   Fr   r   r   r   c           
      (   SSK n[        UR                  5       5      nU R                  R	                  5       nUR                  SXXsUR                  U(       a  SOS45        U R                  R                  5         [        UR                  UUUUUUS9$ )a  Create a new user with a generated UUID.

Args:
    username: Username (display name).
    password_hash: Hashed password.
    locale: Preferred locale.
    trust_level: Initial trust level.
    approved: Whether the account is approved.

Returns:
    Created UserRecord.
r   NzjINSERT INTO users (username, password_hash, uuid, locale, trust_level, approved) VALUES (?, ?, ?, ?, ?, ?)r;   )r   r   r   r   r   r   r   )
r   r   uuid4r3   rM   rE   valuerN   r	   	lastrowid)	r4   r   r   r   r   r   uuid_module	user_uuidrM   s	            r%   create_userDatabase.create_userS  s    ( 	#))+,	""$xi9J9JQYA_`a	
 	

'#
 	
r$   c                     U R                   R                  5       nUR                  SU45        UR                  5       SL$ )zCheck if a user exists.z4SELECT 1 FROM users WHERE lower(username) = lower(?)Nr3   rM   rE   rV   r4   r   rM   s      r%   user_existsDatabase.user_existsz  s8    ""$MPX{[ ,,r$   c                     U R                   R                  5       nUR                  SX!45        U R                   R                  5         g)zUpdate a user's locale.z<UPDATE users SET locale = ? WHERE lower(username) = lower(?)Nr3   rM   rE   rN   )r4   r   r   rM   s       r%   update_user_localeDatabase.update_user_locale  s:    ""$JVL^	
 	

r$   r   c                     U R                   R                  5       nUR                  SX!45        U R                   R                  5         g)zUpdate a user's preferences.zFUPDATE users SET preferences_json = ? WHERE lower(username) = lower(?)Nrv   )r4   r   r   rM   s       r%   update_user_preferences Database.update_user_preferences  s;    ""$T(	
 	

r$   c                     U R                   R                  5       nUR                  SX!45        U R                   R                  5         g)znUpdate a user's password hash.

Args:
    username: Username to update.
    password_hash: New password hash.
zCUPDATE users SET password_hash = ? WHERE lower(username) = lower(?)Nrv   )r4   r   r   rM   s       r%   update_user_passwordDatabase.update_user_password  s=     ""$Q%	
 	

r$   token
expires_at
created_atc                     U R                   R                  5       nUR                  SXX445        U R                   R                  5         g)zStore a new refresh token.zXINSERT INTO refresh_tokens (username, token, expires_at, created_at) VALUES (?, ?, ?, ?)Nrv   )r4   r   r   r   r   rM   s         r%   store_refresh_tokenDatabase.store_refresh_token  s?     ""$fj5	
 	

r$   c                 |    U R                   R                  5       nUR                  SU45        UR                  5       $ )z&Fetch a refresh token record by token.zkSELECT username, token, expires_at, created_at, revoked_at, replaced_by FROM refresh_tokens WHERE token = ?rq   )r4   r   rM   s      r%   get_refresh_tokenDatabase.get_refresh_token  s9    ""$2H	

   r$   
revoked_atreplaced_byc                     U R                   R                  5       nUR                  SX#U45        U R                   R                  5         g)z;Revoke a refresh token and optionally link its replacement.zIUPDATE refresh_tokens SET revoked_at = ?, replaced_by = ? WHERE token = ?Nrv   )r4   r   r   r   rM   s        r%   revoke_refresh_tokenDatabase.revoke_refresh_token  s?     ""$We,	
 	

r$   c                 ~    U R                   R                  5       nUR                  S5        UR                  5       S   $ )z.Get the total number of users in the database.SELECT COUNT(*) FROM usersr   rq   rP   s     r%   get_user_countDatabase.get_user_count  s3    ""$34 ##r$   c                    U R                   R                  5       nUR                  S5        UR                  5       nSn[	        U5      S:X  af  UR                  S5        UR                  5       S   nUS:X  a<  US   S   nUR                  S[        R                  R                  US   S   45        UnUR                  S	[        R                  R                  45        U R                   R                  5         U$ )
a"  
Initialize trust levels for users who don't have one set.

Sets all users without a trust level to USER.
If there's exactly one user and they have no trust level, sets them to SERVER_OWNER.

Returns:
    The username of the user promoted to server owner, or None if no promotion occurred.
z8SELECT id, username FROM users WHERE trust_level IS NULLNr;   r   r   r   z-UPDATE users SET trust_level = ? WHERE id = ?r   z:UPDATE users SET trust_level = ? WHERE trust_level IS NULL)r3   rM   rE   rT   lenrV   r   SERVER_OWNERrj   r    rN   )r4   rM   users_without_trustpromoted_usertotal_usersr   s         r%   initialize_trust_levels Database.initialize_trust_levels  s     ""$ 	QR$oo/"#q(NN78 //+A.Ka.q1*=C,,224G4J44PQ !) 	H:??K`K`Jb	
 	

r$   c                     U R                   R                  5       nUR                  SUR                  U45        U R                   R	                  5         g)zhUpdate a user's trust level.

Args:
    username: Username to update.
    trust_level: New trust level.
zAUPDATE users SET trust_level = ? WHERE lower(username) = lower(?)N)r3   rM   rE   rj   rN   )r4   r   r   rM   s       r%   update_user_trust_level Database.update_user_trust_level  sE     ""$O)	
 	

r$   exclude_bannedc                 ^   U R                   R                  5       nU(       a:  UR                  SU R                   S3[        R
                  R                  45        OUR                  SU R                   S35        UR                  5        Vs/ s H  o0R                  U5      PM     sn$ s  snf )z~Get all users who are not yet approved.

Args:
    exclude_banned: If True (default), excludes banned users from the results.
rc   z2 FROM users WHERE approved = 0 AND trust_level > ?z FROM users WHERE approved = 0	r3   rM   rE   rd   r   BANNEDrj   rT   r`   r4   r   rM   rW   s       r%   get_pending_usersDatabase.get_pending_users  s     ""$NN$,,--_`""((*
 NNWT%7%7$88VWX4:OO4EF4ES##C(4EFFFs   B*c                    U R                   R                  5       nUR                  SU R                   S3[        R
                  R                  45        UR                  5        Vs/ s H  o R                  U5      PM     sn$ s  snf )z@Get all banned users.

Returns:
    List of banned UserRecords.
rc   ! FROM users WHERE trust_level = ?r   r4   rM   rW   s      r%   get_banned_usersDatabase.get_banned_users  st     ""$d(())JK$$&	
 5;OO4EF4ES##C(4EFFFs   &Bc                     U R                   R                  5       nUR                  SU45        U R                   R                  5         UR                  S:  $ )zuApprove a user account.

Args:
    username: Username to approve.

Returns:
    True if user was found and approved.
z>UPDATE users SET approved = 1 WHERE lower(username) = lower(?)r   r3   rM   rE   rN   rowcountrr   s      r%   approve_userDatabase.approve_user  sK     ""$LK	
 	

""r$   c                     U R                   R                  5       nUR                  SU45        U R                   R                  5         UR                  S:  $ )zrDelete a user account.

Args:
    username: Username to delete.

Returns:
    True if user was found and deleted.
z2DELETE FROM users WHERE lower(username) = lower(?)r   r   rr   s      r%   delete_userDatabase.delete_user+  sF     ""$Kh[Y

""r$   c                    U R                   R                  5       nU(       aS  UR                  SU R                   S3[        R
                  R                  [        R                  R                  45        O9UR                  SU R                   S3[        R                  R                  45        UR                  5        Vs/ s H  o0R                  U5      PM     sn$ s  snf )zGet all approved users who are not admins (trust_level < ADMIN).

Args:
    exclude_banned: If True (default), excludes banned users from the results.
rc   zX FROM users WHERE approved = 1 AND trust_level > ? AND trust_level < ? ORDER BY usernamezD FROM users WHERE approved = 1 AND trust_level < ? ORDER BY username)
r3   rM   rE   rd   r   r   rj   ADMINrT   r`   r   s       r%   get_non_admin_usersDatabase.get_non_admin_users9  s     ""$NN$,,-  .F  G""((**:*:*@*@A
 NN$,,--qr!!'') 5;OO4EF4ES##C(4EFFFs    Cc                     U R                   R                  5       nUR                  SU R                   S3[        R
                  R                  45        UR                  5       nU(       a  U R                  U5      $ g)z0Get the server owner (there should only be one).rc   r   N)	r3   rM   rE   rd   r   r   rj   rV   r`   r   s      r%   get_server_ownerDatabase.get_server_ownerL  sk    ""$d(())JK$$**,	
 oo&&s++r$   include_server_ownerc                    U R                   R                  5       nU(       a:  UR                  SU R                   S3[        R
                  R                  45        O9UR                  SU R                   S3[        R
                  R                  45        UR                  5        Vs/ s H  o0R                  U5      PM     sn$ s  snf )zGet all users who are admins (trust_level >= ADMIN).

Args:
    include_server_owner: If True, includes the server owner in the list.
                          If False, only returns admins (not server owner).
rc   z4 FROM users WHERE trust_level >= ? ORDER BY usernamez3 FROM users WHERE trust_level = ? ORDER BY username)	r3   rM   rE   rd   r   r   rj   rT   r`   )r4   r   rM   rW   s       r%   get_admin_usersDatabase.get_admin_usersX  s     ""$NN$,,--ab!!'')
 NN$,,--`a!!'') 5;OO4EF4ES##C(4EFFFs   'Cc                     U R                   R                  5       nUR                  SU45        UR                  5       nU(       a"  [        R
                  " US   =(       d    S5      $ / $ )zpGet the languages a user knows.

Args:
    username: Username to look up.

Returns:
    List of language codes.
zCSELECT fluent_languages FROM users WHERE lower(username) = lower(?)r   r\   )r3   rM   rE   rV   r]   r^   re   s       r%   get_user_fluent_languages"Database.get_user_fluent_languagesn  sZ     ""$QK	
 oo::c"45=>>	r$   	languagesc                     U R                   R                  5       nUR                  S[        R                  " U5      U45        U R                   R                  5         g)z|Replace a user's fluent languages list.

Args:
    username: Username to update.
    languages: New list of language codes.
zFUPDATE users SET fluent_languages = ? WHERE lower(username) = lower(?)N)r3   rM   rE   r]   dumpsrN   )r4   r   r   rM   s       r%   set_user_fluent_languages"Database.set_user_fluent_languages  sH     ""$TZZ	"H-	
 	

r$   c                     U R                   R                  5       nUR                  SU45        UR                  5        Vs/ s H  o3S   PM	     sn$ s  snf )zGet languages a user is approved to transcribe.

Args:
    username: Username to look up.

Returns:
    List of assigned language codes.
zSELECT ta.lang_code FROM transcriber_assignments ta JOIN users u ON ta.user_id = u.id WHERE lower(u.username) = lower(?) ORDER BY ta.lang_code	lang_coder3   rM   rE   rT   re   s       r%   get_transcriber_languages"Database.get_transcriber_languages  sU     ""$G K		
 -3OO,=>,=SK ,=>>>    Ar   c                 (   U R                   R                  5       nUR                  SU45        UR                  5       nU(       d  gUS   n UR                  SXR45        U R                   R	                  5         g! [
        R                   a     gf = f)zAssign a user as transcriber for a language.

Args:
    username: Username to assign.
    lang_code: Language code to assign.

Returns:
    True if added, False if the assignment already exists.
5SELECT id FROM users WHERE lower(username) = lower(?)Fr   zFINSERT INTO transcriber_assignments (user_id, lang_code) VALUES (?, ?)T)r3   rM   rE   rV   rN   r<   rU   r4   r   r   rM   rW   user_ids         r%   add_transcriber_assignment#Database.add_transcriber_assignment  s     ""$NQYP[\ood)	NNX$ JJ%% 		s   -A: :BBc                    U R                   R                  5       nUR                  SU45        UR                  5       nU(       d  gUS   nUR                  SXR45        U R                   R	                  5         UR
                  S:  $ )zRemove a transcriber assignment.

Args:
    username: Username to remove assignment from.
    lang_code: Language code to remove.

Returns:
    True if removed, False if the assignment was not found.
r   Fr   zGDELETE FROM transcriber_assignments WHERE user_id = ? AND lang_code = ?r   )r3   rM   rE   rV   rN   r   r   s         r%   remove_transcriber_assignment&Database.remove_transcriber_assignment  sz     ""$NQYP[\ood)U 	
 	

""r$   c                     U R                   R                  5       nUR                  SU45        UR                  5        Vs/ s H  o3S   PM	     sn$ s  snf )zGet all usernames assigned as transcribers for a language.

Args:
    lang_code: Language code to look up.

Returns:
    List of usernames.
z~SELECT u.username FROM transcriber_assignments ta JOIN users u ON ta.user_id = u.id WHERE ta.lang_code = ? ORDER BY u.usernamer   r   )r4   r   rM   rW   s       r%   get_transcribers_for_language&Database.get_transcribers_for_language  sS     ""$9 L		
 ,2??+<=+<CJ+<===r   c                     U R                   R                  5       nUR                  S5        0 nUR                  5        H*  nUR	                  US   / 5      R                  US   5        M,     U$ )z}Get all transcriber assignments grouped by username.

Returns:
    Dict mapping username to list of assigned language codes.
zSELECT u.username, ta.lang_code FROM transcriber_assignments ta JOIN users u ON ta.user_id = u.id ORDER BY u.username, ta.lang_coder   r   )r3   rM   rE   rT   
setdefaultappend)r4   rM   resultrW   s       r%   get_all_transcribersDatabase.get_all_transcribers  sh     ""$0	

 (*??$Cc*or299#k:JK %r$   tablec           	         U R                   R                  5       n[        R                  " UR                   Vs/ s H  o3R
                  UR                  S.PM     sn5      nUR                  SUR                  UR                  UR                  UUR                  UR                  45        U R                   R                  5         gs  snf )zSave a table to the database.r   is_spectatorz
            INSERT OR REPLACE INTO tables (table_id, game_type, host, members_json, game_json, status)
            VALUES (?, ?, ?, ?, ?, ?)
        N)r3   rM   r]   r   membersr   r   rE   table_idr*   hostr+   statusrN   )r4   r   rM   mr,   s        r%   
save_tableDatabase.save_table  s    ""$ zzOT}}]}!**annE}]
 	
 

	
 	

# ^s   "Cr   c           	      H   U R                   R                  5       nUR                  SU45        UR                  5       nU(       d  g[        R
                  " US   5      nSSKJn  U Vs/ s H  nU" US   US   S9PM     nn[        US	   US
   US   UUS   US   S9$ s  snf )zLoad a table from the database.z'SELECT * FROM tables WHERE table_id = ?Nr,   r   )TableMemberr   r   r   r   r*   r   r+   r   )r   r*   r   r   r+   r   )	r3   rM   rE   rV   r]   r^   server.core.tables.tabler   r   )r4   r   rM   rW   members_datar   r   r   s           r%   
load_tableDatabase.load_table  s    ""$@8+Noo zz#n"568 "
! :Q~=NO! 	 

 _+&V+&x=
 	

s   )Bc                     U R                   R                  5       nUR                  S5        / nUR                  5        H1  nU R	                  US   5      nU(       d  M   UR                  U5        M3     U$ )z"Load all tables from the database.zSELECT table_id FROM tablesr   )r3   rM   rE   rT   r   r   )r4   rM   tablesrW   r   s        r%   load_all_tablesDatabase.load_all_tables,  sa    ""$45??$COOC
O4Eue$ % r$   c                     U R                   R                  5       nUR                  SU45        U R                   R                  5         g)z!Delete a table from the database.z%DELETE FROM tables WHERE table_id = ?Nrv   )r4   r   rM   s      r%   delete_tableDatabase.delete_table7  s5    ""$>L

r$   c                     U R                   R                  5       nUR                  S5        U R                   R                  5         g)z$Delete all tables from the database.zDELETE FROM tablesNrv   rP   s     r%   delete_all_tablesDatabase.delete_all_tables=  s1    ""$+,

r$   r   c                 8    U H  nU R                  U5        M     g)zSave multiple tables.N)r   )r4   r   r   s      r%   save_all_tablesDatabase.save_all_tablesC  s    EOOE" r$   r)   r*   r+   r,   c           
         SSK J n  UR                  5       R                  5       nU R                  R	                  5       nUR                  SXX4XW45        U R                  R                  5         [        UR                  UUUUUUS9$ )z,Save a table state to a user's saved tables.r   )datetimez
            INSERT INTO saved_tables (username, save_name, game_type, game_json, members_json, saved_at)
            VALUES (?, ?, ?, ?, ?, ?)
        r   r   r)   r*   r+   r,   r-   )	r   now	isoformatr3   rM   rE   rN   r'   rk   )	r4   r   r)   r*   r+   r,   r   r-   rM   s	            r%   save_user_tableDatabase.save_user_tableJ  s     	&<<>++-""$ )O	
 	

%
 	
r$   c                     U R                   R                  5       nUR                  SU45        / nUR                  5        H6  nUR	                  [        US   US   US   US   US   US   US   S	95        M8     U$ )
z Get all saved tables for a user.zRSELECT * FROM saved_tables WHERE lower(username) = lower(?) ORDER BY saved_at DESCr   r   r)   r*   r+   r,   r-   r   )r3   rM   rE   rT   r   r'   )r4   r   rM   recordsrW   s        r%   get_user_saved_tablesDatabase.get_user_saved_tablesk  s    ""$`K	
 ??$CNN 4y _!+.!+.!+.!$^!4 _
 % r$   save_idc           
          U R                   R                  5       nUR                  SU45        UR                  5       nU(       d  g[	        US   US   US   US   US   US   US	   S
9$ )zGet a saved table by ID.z'SELECT * FROM saved_tables WHERE id = ?Nr   r   r)   r*   r+   r,   r-   r   )r3   rM   rE   rV   r'   )r4   r  rM   rW   s       r%   get_saved_tableDatabase.get_saved_table  sy    ""$@7*Moo4y_+&+&+&^,_
 	
r$   c                     U R                   R                  5       nUR                  SU45        U R                   R                  5         g)zDelete a saved table.z%DELETE FROM saved_tables WHERE id = ?Nrv   )r4   r  rM   s      r%   delete_saved_tableDatabase.delete_saved_table  s5    ""$>
K

r$   	timestampduration_ticksplayerscustom_datac           	      R   U R                   R                  5       nUR                  SUUUU(       a  [        R                  " U5      OS45        UR
                  nU H-  u  ppUR                  SXxX(       a  SOSU(       a  SOS45        M/     U R                   R                  5         U$ )a7  
Save a game result to the database.

Args:
    game_type: The game type identifier
    timestamp: ISO format timestamp
    duration_ticks: Game duration in ticks
    players: List of (player_id, player_name, is_bot, is_virtual_bot) tuples
    custom_data: Game-specific result data

Returns:
    The result ID
z
            INSERT INTO game_results (game_type, timestamp, duration_ticks, custom_data)
            VALUES (?, ?, ?, ?)
            Nz
                INSERT INTO game_result_players (result_id, player_id, player_name, is_bot, is_virtual_bot)
                VALUES (?, ?, ?, ?, ?)
                r;   r   )r3   rM   rE   r]   r   rk   rN   )r4   r*   r  r  r  r  rM   	result_id	player_idplayer_nameis_botrS   s               r%   save_game_resultDatabase.save_game_result  s    . ""$ 	
 +6

;'D		
 $$	 ?F:IFNN {AQ^abc ?F 	

r$   r  limitc                 V   U R                   R                  5       nU(       a  UR                  SXU45        OUR                  SX45        / nUR                  5        HI  nUR	                  US   US   US   US   US   (       a  [
        R                  " US   5      O0 S.5        MK     U$ )	z
Get a player's game history.

Args:
    player_id: The player ID to look up
    game_type: Optional filter by game type
    limit: Maximum number of results

Returns:
    List of game result dictionaries
a^  
                SELECT gr.id, gr.game_type, gr.timestamp, gr.duration_ticks, gr.custom_data
                FROM game_results gr
                INNER JOIN game_result_players grp ON gr.id = grp.result_id
                WHERE grp.player_id = ? AND gr.game_type = ?
                ORDER BY gr.timestamp DESC
                LIMIT ?
                aI  
                SELECT gr.id, gr.game_type, gr.timestamp, gr.duration_ticks, gr.custom_data
                FROM game_results gr
                INNER JOIN game_result_players grp ON gr.id = grp.result_id
                WHERE grp.player_id = ?
                ORDER BY gr.timestamp DESC
                LIMIT ?
                r   r*   r  r  r  )r   r*   r  r  r  )r3   rM   rE   rT   r   r]   r^   )r4   r  r*   r  rM   resultsrW   s          r%   get_player_game_history Database.get_player_game_history  s    " ""$NN u-
 NN "
 ??$CNNd)!$[!1!$[!1&)*:&;EHEW4::c-.@#A]_ % r$   r  c           
          U R                   R                  5       nUR                  SU45        UR                  5        Vs/ s H0  nUS   US   [	        US   5      US   b  [	        US   5      OSS.PM2     sn$ s  snf )z+Get all players for a specific game result.z
            SELECT player_id, player_name, is_bot, is_virtual_bot
            FROM game_result_players
            WHERE result_id = ?
            r  r  r  rS   F)r  r  r  rS   )r3   rM   rE   rT   r!   )r4   r  rM   rW   s       r%   get_game_result_players Database.get_game_result_players	  s    ""$
 L	
" (

 ) !-"=1s8}-'(4 #'s+;'<"= )

 
	
 

s    7A:c           	         U R                   R                  5       nU(       a  UR                  SX45        OUR                  SU45        UR                  5        Vs/ s H  nUS   US   US   US   US   4PM     sn$ s  snf )z
Get game results for a game type.

Args:
    game_type: The game type to query
    limit: Optional maximum number of results

Returns:
    List of tuples: (id, game_type, timestamp, duration_ticks, custom_data)
z
                SELECT id, game_type, timestamp, duration_ticks, custom_data
                FROM game_results
                WHERE game_type = ?
                ORDER BY timestamp DESC
                LIMIT ?
                z
                SELECT id, game_type, timestamp, duration_ticks, custom_data
                FROM game_results
                WHERE game_type = ?
                ORDER BY timestamp DESC
                r   r*   r  r  r  r   r4   r*   r  rM   rW   s        r%   get_game_statsDatabase.get_game_stats   s     ""$NN "	 NN $ (	
 ) D	K K $%M" )	
 		
 	
s    A>c                     U R                   R                  5       nUR                  SU45        UR                  5       nUS   =(       d    SUS   =(       d    SUS   =(       d    SS.$ )zq
Get aggregate statistics for a game type.

Returns:
    Dictionary with total_games, total_duration_ticks, etc.
z
            SELECT
                COUNT(*) as total_games,
                SUM(duration_ticks) as total_duration,
                AVG(duration_ticks) as avg_duration
            FROM game_results
            WHERE game_type = ?
            total_gamesr   total_durationavg_duration)r#  total_duration_ticksavg_duration_ticksrq   )r4   r*   rM   rW   s       r%   get_game_stats_aggregate!Database.get_game_stats_aggregateN  sn     ""$ L
	
 oo}-2$'(8$9$>Q"%n"5":
 	
r$   c                     U R                   R                  5       nU(       a  UR                  SX45        OUR                  SU45        UR                  5       nSUS   =(       d    S0$ )z
Get statistics for a player.

Args:
    player_id: The player ID
    game_type: Optional filter by game type

Returns:
    Dictionary with games_played, etc.
z
                SELECT COUNT(*) as games_played
                FROM game_result_players grp
                INNER JOIN game_results gr ON grp.result_id = gr.id
                WHERE grp.player_id = ? AND gr.game_type = ?
                z
                SELECT COUNT(*) as games_played
                FROM game_result_players
                WHERE player_id = ?
                games_playedr   rq   r4   r  r*   rM   rW   s        r%   get_player_statsDatabase.get_player_statsh  ss     ""$NN & NN
  ooC/41
 	
r$   c                     U R                   R                  5       nUR                  SX45        UR                  5       nU(       a
  US   US   4$ g)zd
Get a player's rating for a game type.

Returns:
    (mu, sigma) tuple or None if no rating exists
zp
            SELECT mu, sigma FROM player_ratings
            WHERE player_id = ? AND game_type = ?
            musigmaNrq   r,  s        r%   get_player_ratingDatabase.get_player_rating  sV     ""$ "	
 ooIs7|,,r$   r0  r1  c                     U R                   R                  5       nUR                  SXX445        U R                   R                  5         g)z0Set or update a player's rating for a game type.z
            INSERT OR REPLACE INTO player_ratings (player_id, game_type, mu, sigma)
            VALUES (?, ?, ?, ?)
            Nrv   )r4   r  r*   r0  r1  rM   s         r%   set_player_ratingDatabase.set_player_rating  sA    ""$ 2-	
 	

r$   c                     U R                   R                  5       nUR                  SX45        UR                  5        Vs/ s H  oDS   US   US   4PM     sn$ s  snf )zy
Get the rating leaderboard for a game type.

Returns:
    List of (player_id, mu, sigma) tuples sorted by mu descending
z
            SELECT player_id, mu, sigma FROM player_ratings
            WHERE game_type = ?
            ORDER BY mu DESC
            LIMIT ?
            r  r0  r1  r   r  s        r%   get_rating_leaderboardDatabase.get_rating_leaderboard  sf     ""$ 	
 HNGXYGX[!3t9c'l;GXYYYs    Ac                     U R                   R                  5       nUR                  S5        U R                   R                  5         g)z.Create virtual_bots table if it doesn't exist.ao  
            CREATE TABLE IF NOT EXISTS virtual_bots (
                name TEXT PRIMARY KEY,
                state TEXT NOT NULL,
                online_ticks INTEGER NOT NULL DEFAULT 0,
                target_online_ticks INTEGER NOT NULL DEFAULT 0,
                table_id TEXT,
                game_join_tick INTEGER NOT NULL DEFAULT 0
            )
            Nrv   rP   s     r%   _ensure_virtual_bots_table#Database._ensure_virtual_bots_table  s7    ""$		
 	

r$   namestateonline_tickstarget_online_ticksgame_join_tickc           	          U R                  5         U R                  R                  5       nUR                  SXX4XV45        U R                  R	                  5         g)z%Save or update a virtual bot's state.z
            INSERT OR REPLACE INTO virtual_bots
            (name, state, online_ticks, target_online_ticks, table_id, game_join_tick)
            VALUES (?, ?, ?, ?, ?, ?)
            Nr;  r3   rM   rE   rN   )r4   r=  r>  r?  r@  r   rA  rM   s           r%   save_virtual_botDatabase.save_virtual_bot  sQ     	'')""$
 ,XV	
 	

r$   c           
          U R                  5         U R                  R                  5       nUR                  S5        UR	                  5        Vs/ s H  nUS   US   US   US   US   US   S.PM      sn$ s  snf )	z.Load all virtual bot states from the database.z
            SELECT name, state, online_ticks, target_online_ticks, table_id, game_join_tick
            FROM virtual_bots
            r=  r>  r?  r@  r   rA  )r=  r>  r?  r@  r   rA  )r;  r3   rM   rE   rT   r   s      r%   load_all_virtual_botsDatabase.load_all_virtual_bots  s    '')""$	
 (

 ) FW #N 3'*+@'A
O"%&6"7 )

 
	
 

s   %A6c                     U R                  5         U R                  R                  5       nUR                  SU45        U R                  R	                  5         g)z.Delete a single virtual bot from the database.z'DELETE FROM virtual_bots WHERE name = ?NrC  )r4   r=  rM   s      r%   delete_virtual_botDatabase.delete_virtual_bot  sA    '')""$@4'J

r$   c                     U R                  5         U R                  R                  5       nUR                  S5        U R                  R	                  5         g)z*Delete all virtual bots from the database.zDELETE FROM virtual_botsNrC  rP   s     r%   delete_all_virtual_bots Database.delete_all_virtual_bots  s=    '')""$12

r$   )r3   r1   )zplaypalace.db)r7   N)N)T)N2   )
   )Or   r   r   r   r   r   r   r5   r=   rJ   rF   rO   staticmethodr<   rC   r	   r`   rd   rf   r   r    r!   rn   rs   rw   rz   r}   r   r   r   r   r   r   r   r"   r   r   r   r   r   r   r   r   r   r   r   r   r   dictr   r   r   r   r   r   r   r   r'   r   r  r  r	  tupler  r  r  r   r(  r-  floatr2  r5  r8  r;  rD  rG  rJ  rM  r#   r   r$   r%   r/   r/   ?   s   
5d
 5
}~T p 
GKK 
J 
 
 {M
 
d): 
  ",//%
%
 %
 	%

  %
 %
 
%
N-C -D -3    s t S    		$'	58	FI			!s !w{{T/A ! FJ		&)	8;d
			$ $&t &P * QU G GZ@P G G$z"2 G#S #T #"#C #D #G$ G$zBR G&
*t"3 
GD GDDT G,# $s) &# $s) PT  ?# ?$s) ?$3 3 4 4#c #c #d #.>s >tCy >$d3S	>&: & $ 2
3 
54< 
4	e 	S T #d5k #d #

 
 	

 
 
 

Bc d;K6L ,
s 
/?$/F 
$# $   $(33 3 	3
 #sD$&'
3 D[3 
3p !%	77 :7 	7
 
d7r
 
d 
.,
 ,
C$J ,
$u+ ,
\
# 
$ 
4$
# $
#* $
PT $
P3 3 5PUCVY]C] (
3 
3 
E 
RW 
\` 
 ,.ZZ%(Z	eC%&	'Z."  	
 ! *  
,
tDz 
,s t r$   r/   )r   r<   r@   r]   pathlibr   dataclassesr   r   r   r   server.core.users.baser   r	   r'   r/   r   r$   r%   <module>rX     s[    &  
   ( * - > > >4   ,Q Qr$   