pub mod clients;
pub mod groups;
pub mod pools;
pub mod tokens;
pub mod users;

pub use aws_local_core::db::{connect_in_memory, DbPool};

pub fn connect(data_dir: &str) -> rusqlite::Result<DbPool> {
    aws_local_core::db::connect(data_dir, "cognito.db")
}

pub fn run_migrations(db: &DbPool) -> rusqlite::Result<()> {
    let conn = db.lock().unwrap();
    conn.execute_batch(
        "CREATE TABLE IF NOT EXISTS user_pools (
            id              TEXT PRIMARY KEY,
            name            TEXT NOT NULL,
            config_json     TEXT NOT NULL DEFAULT '{}',
            created_at      TEXT NOT NULL,
            updated_at      TEXT NOT NULL
        );

        CREATE TABLE IF NOT EXISTS users (
            pool_id         TEXT NOT NULL REFERENCES user_pools(id) ON DELETE CASCADE,
            username        TEXT NOT NULL,
            sub             TEXT NOT NULL,
            password_hash   TEXT,
            status          TEXT NOT NULL DEFAULT 'UNCONFIRMED',
            enabled         INTEGER NOT NULL DEFAULT 1,
            attributes_json TEXT NOT NULL DEFAULT '[]',
            created_at      TEXT NOT NULL,
            updated_at      TEXT NOT NULL,
            PRIMARY KEY (pool_id, username)
        );

        CREATE TABLE IF NOT EXISTS groups (
            pool_id         TEXT NOT NULL REFERENCES user_pools(id) ON DELETE CASCADE,
            group_name      TEXT NOT NULL,
            description     TEXT,
            precedence      INTEGER,
            created_at      TEXT NOT NULL,
            updated_at      TEXT NOT NULL,
            PRIMARY KEY (pool_id, group_name)
        );

        CREATE TABLE IF NOT EXISTS user_groups (
            pool_id         TEXT NOT NULL,
            username        TEXT NOT NULL,
            group_name      TEXT NOT NULL,
            PRIMARY KEY (pool_id, username, group_name),
            FOREIGN KEY (pool_id, username) REFERENCES users(pool_id, username) ON DELETE CASCADE,
            FOREIGN KEY (pool_id, group_name) REFERENCES groups(pool_id, group_name) ON DELETE CASCADE
        );

        CREATE TABLE IF NOT EXISTS refresh_tokens (
            token           TEXT PRIMARY KEY,
            pool_id         TEXT NOT NULL,
            username        TEXT NOT NULL,
            created_at      TEXT NOT NULL,
            expires_at      TEXT NOT NULL,
            FOREIGN KEY (pool_id, username) REFERENCES users(pool_id, username) ON DELETE CASCADE
        );

        CREATE TABLE IF NOT EXISTS user_pool_clients (
            client_id       TEXT PRIMARY KEY,
            pool_id         TEXT NOT NULL REFERENCES user_pools(id) ON DELETE CASCADE,
            client_name     TEXT NOT NULL,
            config_json     TEXT NOT NULL DEFAULT '{}',
            created_at      TEXT NOT NULL,
            updated_at      TEXT NOT NULL
        );"
    )?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_in_memory_connect_and_migrate() {
        let db = connect_in_memory().unwrap();
        run_migrations(&db).unwrap();

        let conn = db.lock().unwrap();
        let count: i64 = conn
            .query_row(
                "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('user_pools','users','groups','user_groups','refresh_tokens','user_pool_clients')",
                [],
                |row| row.get(0),
            )
            .unwrap();
        assert_eq!(count, 6);
    }

    #[test]
    fn test_foreign_keys_enabled() {
        let db = connect_in_memory().unwrap();
        run_migrations(&db).unwrap();

        let conn = db.lock().unwrap();
        let fk: i64 = conn
            .query_row("PRAGMA foreign_keys", [], |row| row.get(0))
            .unwrap();
        assert_eq!(fk, 1);
    }

    #[test]
    fn test_migrations_idempotent() {
        let db = connect_in_memory().unwrap();
        run_migrations(&db).unwrap();
        // Running migrations again should not error
        run_migrations(&db).unwrap();
    }
}
