Skip to content
use crate::db::Database;
use migs::{collect, migs};

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS users (
        id TEXT PRIMARY KEY,
        username TEXT UNIQUE NOT NULL,
        email TEXT,
        password_hash TEXT NOT NULL,
        created_at TEXT NOT NULL
    );"#,
    scope = "init",
    order = 1
}

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS tokens (
        token TEXT PRIMARY KEY,
        user_id TEXT NOT NULL,
        created_at TEXT NOT NULL,
        FOREIGN KEY (user_id) REFERENCES users(id)
    );"#,
    scope = "init",
    order = 2
}

migs! {
    sql = r#"CREATE INDEX IF NOT EXISTS idx_tokens_created_at ON tokens(created_at);"#,
    scope = "init",
    order = 3
}

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS invite_links (
        code TEXT PRIMARY KEY,
        created_at TEXT NOT NULL
    );"#,
    scope = "v2",
    order = 4
}

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS exercises (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        description TEXT NOT NULL DEFAULT '',
        unit TEXT NOT NULL DEFAULT 'reps',
        points_per_unit REAL NOT NULL DEFAULT 1.0,
        created_at TEXT NOT NULL
    );"#,
    scope = "v2",
    order = 5
}

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS goals (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        target_points REAL NOT NULL,
        created_at TEXT NOT NULL
    );"#,
    scope = "v2",
    order = 6
}

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS exercise_logs (
        id TEXT PRIMARY KEY,
        user_id TEXT NOT NULL,
        exercise_id TEXT NOT NULL,
        amount REAL NOT NULL,
        points_earned REAL NOT NULL,
        created_at TEXT NOT NULL,
        FOREIGN KEY (user_id) REFERENCES users(id),
        FOREIGN KEY (exercise_id) REFERENCES exercises(id)
    );"#,
    scope = "v2",
    order = 7
}

migs! {
    sql = r#"CREATE TABLE IF NOT EXISTS favorite_exercises (
        user_id TEXT NOT NULL,
        exercise_id TEXT NOT NULL,
        PRIMARY KEY (user_id, exercise_id),
        FOREIGN KEY (user_id) REFERENCES users(id),
        FOREIGN KEY (exercise_id) REFERENCES exercises(id)
    );"#,
    scope = "v2",
    order = 8
}

migs! {
    sql = r#"CREATE INDEX IF NOT EXISTS idx_exercise_logs_user ON exercise_logs(user_id);"#,
    scope = "v2",
    order = 9
}

migs! {
    sql = r#"CREATE INDEX IF NOT EXISTS idx_exercise_logs_exercise ON exercise_logs(exercise_id);"#,
    scope = "v2",
    order = 10
}

migs! {
    sql = r#"DROP TABLE IF EXISTS team_members;"#,
    scope = "v2",
    order = 11
}

migs! {
    sql = r#"DROP TABLE IF EXISTS teams;"#,
    scope = "v2",
    order = 12
}

migs! {
    sql = r#"ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0;"#,
    scope = "v3",
    order = 13
}

impl Database {
    pub async fn create_tables(&self) -> Result<(), String> {
        let _ = self
            .conn()
            .await?
            .query("PRAGMA journal_mode = WAL;", ())
            .await
            .map_err(|e| e.to_string())?;

        let migrations = collect!();

        let mut migrations: Vec<_> = migrations;
        migrations.sort_by_key(|m| m.order.unwrap_or(u32::MAX));

        for migration in &migrations {
            let result = self.conn().await?.execute(migration.content, ()).await;

            match result {
                Ok(_) => {}
                Err(e)
                    if migration.content.trim().starts_with("ALTER TABLE")
                        && (e.to_string().contains("duplicate column name")
                            || e.to_string().contains("no such table")) =>
                {
                    log::debug!("Skipping already-applied migration: {e}");
                }
                Err(e) => return Err(format!("Migration failed: {e}")),
            }
        }

        Ok(())
    }
}