mod library;
mod storage;

use library::commands::{
    launch_library_game, list_library_games, scan_library_command, update_library_game,
};
use rusqlite::{params, Connection, OptionalExtension};
use serde::Serialize;
use std::time::{SystemTime, UNIX_EPOCH};
use storage::AppStorage;
use tauri::Manager;

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct UserRecord {
    id: i64,
    name: String,
    first_name: String,
    last_name: Option<String>,
    created_at_unix_ms: i64,
}

fn now_unix_ms() -> i64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_millis() as i64)
        .unwrap_or(0)
}

fn sanitize_name(name: &str) -> Result<String, String> {
    let trimmed = name.trim();
    if trimmed.is_empty() {
        return Err("User name cannot be empty.".to_string());
    }
    if trimmed.chars().count() > 64 {
        return Err("User name is too long (max 64 chars).".to_string());
    }
    Ok(trimmed.to_string())
}

fn sanitize_optional_name(name: Option<String>) -> Result<Option<String>, String> {
    let Some(value) = name else {
        return Ok(None);
    };
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return Ok(None);
    }
    if trimmed.chars().count() > 64 {
        return Err("Name is too long (max 64 chars).".to_string());
    }
    Ok(Some(trimmed.to_string()))
}

fn build_display_name(first_name: &str, last_name: Option<&str>) -> String {
    let last = last_name.map(str::trim).unwrap_or("");
    if last.is_empty() {
        first_name.to_string()
    } else {
        format!("{first_name} {last}")
    }
}

fn ensure_users_schema(conn: &Connection) -> Result<(), String> {
    conn.execute(
        "CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            created_at_unix_ms INTEGER NOT NULL
        )",
        [],
    )
    .map_err(|err| format!("Failed to prepare users table: {err}"))?;

    let mut stmt = conn
        .prepare("PRAGMA table_info(users)")
        .map_err(|err| format!("Failed to inspect users schema: {err}"))?;
    let mut rows = stmt
        .query([])
        .map_err(|err| format!("Failed to read users schema: {err}"))?;

    let mut has_first_name = false;
    let mut has_last_name = false;
    while let Some(row) = rows
        .next()
        .map_err(|err| format!("Failed to iterate users schema: {err}"))?
    {
        let column_name: String = row
            .get(1)
            .map_err(|err| format!("Failed to parse users schema: {err}"))?;
        if column_name == "first_name" {
            has_first_name = true;
        }
        if column_name == "last_name" {
            has_last_name = true;
        }
    }

    if !has_first_name {
        conn.execute("ALTER TABLE users ADD COLUMN first_name TEXT", [])
            .map_err(|err| format!("Failed to add first_name column: {err}"))?;
        conn.execute(
            "UPDATE users SET first_name = TRIM(name) WHERE first_name IS NULL OR TRIM(first_name) = ''",
            [],
        )
        .map_err(|err| format!("Failed to backfill first_name: {err}"))?;
    }

    if !has_last_name {
        conn.execute("ALTER TABLE users ADD COLUMN last_name TEXT", [])
            .map_err(|err| format!("Failed to add last_name column: {err}"))?;
    }

    Ok(())
}

#[tauri::command]
fn get_first_user(storage: tauri::State<'_, AppStorage>) -> Result<Option<UserRecord>, String> {
    let conn = storage.connect_users()?;
    ensure_users_schema(&conn)?;
    conn.query_row(
        "SELECT
            id,
            COALESCE(
                NULLIF(TRIM(COALESCE(first_name, '') || ' ' || COALESCE(last_name, '')), ''),
                name
            ) AS display_name,
            COALESCE(NULLIF(TRIM(first_name), ''), name) AS first_name,
            NULLIF(TRIM(COALESCE(last_name, '')), '') AS last_name,
            created_at_unix_ms
         FROM users
         ORDER BY id ASC
         LIMIT 1",
        [],
        |row| {
            Ok(UserRecord {
                id: row.get(0)?,
                name: row.get(1)?,
                first_name: row.get(2)?,
                last_name: row.get(3)?,
                created_at_unix_ms: row.get(4)?,
            })
        },
    )
    .optional()
    .map_err(|err| format!("Failed to load user: {err}"))
}

#[tauri::command]
fn create_user(
    first_name: String,
    last_name: Option<String>,
    storage: tauri::State<'_, AppStorage>,
) -> Result<UserRecord, String> {
    let safe_first_name = sanitize_name(&first_name)?;
    let safe_last_name = sanitize_optional_name(last_name)?;
    let display_name = build_display_name(&safe_first_name, safe_last_name.as_deref());
    let created_at_unix_ms = now_unix_ms();
    let conn = storage.connect_users()?;
    ensure_users_schema(&conn)?;
    conn.execute(
        "INSERT INTO users (name, first_name, last_name, created_at_unix_ms) VALUES (?1, ?2, ?3, ?4)",
        params![
            display_name,
            safe_first_name,
            safe_last_name,
            created_at_unix_ms
        ],
    )
    .map_err(|err| format!("Failed to create user: {err}"))?;

    let id = conn.last_insert_rowid();
    Ok(UserRecord {
        id,
        name: build_display_name(&safe_first_name, safe_last_name.as_deref()),
        first_name: safe_first_name,
        last_name: safe_last_name,
        created_at_unix_ms,
    })
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .setup(|app| {
            let app_data_dir = app
                .path()
                .app_data_dir()
                .map_err(|err| format!("Failed to resolve app data dir: {err}"))?;
            let storage = AppStorage::new(app_data_dir)?;
            let users_conn = storage.connect_users()?;
            ensure_users_schema(&users_conn)?;
            drop(users_conn);
            library::initialize(&storage)?;

            app.manage(storage);
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            get_first_user,
            create_user,
            list_library_games,
            launch_library_game,
            scan_library_command,
            update_library_game
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
