use crate::db::DbPool;
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::Argon2;
use chrono::Utc;
use rusqlite::Row;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserAttribute {
    #[serde(rename = "Name")]
    pub name: String,
    #[serde(rename = "Value")]
    pub value: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub pool_id: String,
    pub username: String,
    pub sub: String,
    pub password_hash: Option<String>,
    pub status: String,
    pub enabled: bool,
    pub attributes: Vec<UserAttribute>,
    pub created_at: String,
    pub updated_at: String,
}

pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
    let salt = SaltString::generate(&mut OsRng);
    let argon2 = Argon2::default();
    let hash = argon2.hash_password(password.as_bytes(), &salt)?;
    Ok(hash.to_string())
}

pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
    let parsed = PasswordHash::new(hash)?;
    Ok(Argon2::default()
        .verify_password(password.as_bytes(), &parsed)
        .is_ok())
}

fn row_to_user(row: &Row<'_>) -> rusqlite::Result<User> {
    let attrs_json: String = row.get(6)?;
    let attributes: Vec<UserAttribute> =
        serde_json::from_str(&attrs_json).unwrap_or_default();
    Ok(User {
        pool_id: row.get(0)?,
        username: row.get(1)?,
        sub: row.get(2)?,
        password_hash: row.get(3)?,
        status: row.get(4)?,
        enabled: row.get(5)?,
        attributes,
        created_at: row.get(7)?,
        updated_at: row.get(8)?,
    })
}

pub fn create(
    db: &DbPool,
    pool_id: &str,
    username: &str,
    password_hash: Option<&str>,
    status: &str,
    attributes: &[UserAttribute],
) -> rusqlite::Result<User> {
    let conn = db.lock().unwrap();
    let now = Utc::now().to_rfc3339();
    let sub = Uuid::new_v4().to_string();
    let attrs_json = serde_json::to_string(attributes).unwrap_or_else(|_| "[]".to_string());

    conn.execute(
        "INSERT INTO users (pool_id, username, sub, password_hash, status, enabled, attributes_json, created_at, updated_at)
         VALUES (?1, ?2, ?3, ?4, ?5, 1, ?6, ?7, ?8)",
        rusqlite::params![pool_id, username, sub, password_hash, status, attrs_json, now, now],
    )?;

    Ok(User {
        pool_id: pool_id.to_string(),
        username: username.to_string(),
        sub,
        password_hash: password_hash.map(|s| s.to_string()),
        status: status.to_string(),
        enabled: true,
        attributes: attributes.to_vec(),
        created_at: now.clone(),
        updated_at: now,
    })
}

pub fn get(db: &DbPool, pool_id: &str, username: &str) -> rusqlite::Result<Option<User>> {
    let conn = db.lock().unwrap();
    let mut stmt = conn.prepare(
        "SELECT pool_id, username, sub, password_hash, status, enabled, attributes_json, created_at, updated_at
         FROM users WHERE pool_id = ?1 AND username = ?2",
    )?;
    let mut rows = stmt.query(rusqlite::params![pool_id, username])?;
    match rows.next()? {
        Some(row) => Ok(Some(row_to_user(row)?)),
        None => Ok(None),
    }
}

pub fn get_by_sub(db: &DbPool, pool_id: &str, sub: &str) -> rusqlite::Result<Option<User>> {
    let conn = db.lock().unwrap();
    let mut stmt = conn.prepare(
        "SELECT pool_id, username, sub, password_hash, status, enabled, attributes_json, created_at, updated_at
         FROM users WHERE pool_id = ?1 AND sub = ?2",
    )?;
    let mut rows = stmt.query(rusqlite::params![pool_id, sub])?;
    match rows.next()? {
        Some(row) => Ok(Some(row_to_user(row)?)),
        None => Ok(None),
    }
}

pub fn list(db: &DbPool, pool_id: &str, limit: usize) -> rusqlite::Result<Vec<User>> {
    let conn = db.lock().unwrap();
    let mut stmt = conn.prepare(
        "SELECT pool_id, username, sub, password_hash, status, enabled, attributes_json, created_at, updated_at
         FROM users WHERE pool_id = ?1 ORDER BY created_at LIMIT ?2",
    )?;
    let rows = stmt.query_map(rusqlite::params![pool_id, limit as i64], |row| {
        row_to_user(row)
    })?;
    rows.collect()
}

pub fn delete(db: &DbPool, pool_id: &str, username: &str) -> rusqlite::Result<bool> {
    let conn = db.lock().unwrap();
    let affected = conn.execute(
        "DELETE FROM users WHERE pool_id = ?1 AND username = ?2",
        rusqlite::params![pool_id, username],
    )?;
    Ok(affected > 0)
}

pub fn set_password(
    db: &DbPool,
    pool_id: &str,
    username: &str,
    hash: &str,
    permanent: bool,
) -> rusqlite::Result<bool> {
    let conn = db.lock().unwrap();
    let now = Utc::now().to_rfc3339();

    let affected = if permanent {
        conn.execute(
            "UPDATE users SET password_hash = ?1, status = 'CONFIRMED', updated_at = ?2
             WHERE pool_id = ?3 AND username = ?4",
            rusqlite::params![hash, now, pool_id, username],
        )?
    } else {
        conn.execute(
            "UPDATE users SET password_hash = ?1, updated_at = ?2
             WHERE pool_id = ?3 AND username = ?4",
            rusqlite::params![hash, now, pool_id, username],
        )?
    };
    Ok(affected > 0)
}

pub fn update_attributes(
    db: &DbPool,
    pool_id: &str,
    username: &str,
    new_attrs: &[UserAttribute],
) -> rusqlite::Result<bool> {
    let conn = db.lock().unwrap();
    let now = Utc::now().to_rfc3339();

    // Fetch existing attributes
    let existing_json: String = {
        let mut stmt = conn.prepare(
            "SELECT attributes_json FROM users WHERE pool_id = ?1 AND username = ?2",
        )?;
        let mut rows = stmt.query(rusqlite::params![pool_id, username])?;
        match rows.next()? {
            Some(row) => row.get(0)?,
            None => return Ok(false),
        }
    };

    let mut existing: Vec<UserAttribute> =
        serde_json::from_str(&existing_json).unwrap_or_default();

    // Merge: update matching names, append new ones
    for new_attr in new_attrs {
        if let Some(existing_attr) = existing.iter_mut().find(|a| a.name == new_attr.name) {
            existing_attr.value.clone_from(&new_attr.value);
        } else {
            existing.push(new_attr.clone());
        }
    }

    let merged_json = serde_json::to_string(&existing).unwrap_or_else(|_| "[]".to_string());

    conn.execute(
        "UPDATE users SET attributes_json = ?1, updated_at = ?2
         WHERE pool_id = ?3 AND username = ?4",
        rusqlite::params![merged_json, now, pool_id, username],
    )?;
    Ok(true)
}

pub fn update_status(
    db: &DbPool,
    pool_id: &str,
    username: &str,
    status: &str,
) -> rusqlite::Result<bool> {
    let conn = db.lock().unwrap();
    let now = Utc::now().to_rfc3339();
    let affected = conn.execute(
        "UPDATE users SET status = ?1, updated_at = ?2
         WHERE pool_id = ?3 AND username = ?4",
        rusqlite::params![status, now, pool_id, username],
    )?;
    Ok(affected > 0)
}

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

    fn setup() -> DbPool {
        let pool = db::connect_in_memory().unwrap();
        db::run_migrations(&pool).unwrap();
        db::pools::create(&pool, "us-east-1_testpool", "TestPool", &serde_json::json!({}))
            .unwrap();
        pool
    }

    #[test]
    fn test_hash_and_verify_password() {
        let hash = hash_password("supersecret").unwrap();
        assert!(verify_password("supersecret", &hash).unwrap());
        assert!(!verify_password("wrongpassword", &hash).unwrap());
    }

    #[test]
    fn test_create_and_get_user() {
        let db = setup();
        let attrs = vec![UserAttribute {
            name: "email".to_string(),
            value: "test@example.com".to_string(),
        }];
        let user = create(
            &db,
            "us-east-1_testpool",
            "alice",
            Some("fakehash"),
            "CONFIRMED",
            &attrs,
        )
        .unwrap();

        assert_eq!(user.username, "alice");
        assert_eq!(user.status, "CONFIRMED");
        assert!(user.enabled);
        assert!(!user.sub.is_empty());

        let fetched = get(&db, "us-east-1_testpool", "alice").unwrap().unwrap();
        assert_eq!(fetched.username, "alice");
        assert_eq!(fetched.sub, user.sub);
        assert_eq!(fetched.attributes.len(), 1);
        assert_eq!(fetched.attributes[0].name, "email");
        assert_eq!(fetched.attributes[0].value, "test@example.com");

        // Also test get_by_sub
        let by_sub = get_by_sub(&db, "us-east-1_testpool", &user.sub)
            .unwrap()
            .unwrap();
        assert_eq!(by_sub.username, "alice");
    }

    #[test]
    fn test_list_users() {
        let db = setup();
        create(&db, "us-east-1_testpool", "user1", None, "CONFIRMED", &[]).unwrap();
        create(&db, "us-east-1_testpool", "user2", None, "CONFIRMED", &[]).unwrap();
        create(&db, "us-east-1_testpool", "user3", None, "CONFIRMED", &[]).unwrap();

        let users = list(&db, "us-east-1_testpool", 10).unwrap();
        assert_eq!(users.len(), 3);

        // Test limit
        let limited = list(&db, "us-east-1_testpool", 2).unwrap();
        assert_eq!(limited.len(), 2);
    }

    #[test]
    fn test_delete_user() {
        let db = setup();
        create(&db, "us-east-1_testpool", "todelete", None, "CONFIRMED", &[]).unwrap();
        assert!(delete(&db, "us-east-1_testpool", "todelete").unwrap());
        assert!(get(&db, "us-east-1_testpool", "todelete").unwrap().is_none());

        // Deleting nonexistent user returns false
        assert!(!delete(&db, "us-east-1_testpool", "nonexistent").unwrap());
    }

    #[test]
    fn test_set_password_permanent() {
        let db = setup();
        create(
            &db,
            "us-east-1_testpool",
            "bob",
            Some("oldhash"),
            "FORCE_CHANGE_PASSWORD",
            &[],
        )
        .unwrap();

        let updated = set_password(&db, "us-east-1_testpool", "bob", "newhash", true).unwrap();
        assert!(updated);

        let user = get(&db, "us-east-1_testpool", "bob").unwrap().unwrap();
        assert_eq!(user.password_hash.as_deref(), Some("newhash"));
        assert_eq!(user.status, "CONFIRMED");
    }

    #[test]
    fn test_update_attributes_merge() {
        let db = setup();
        let initial = vec![
            UserAttribute {
                name: "email".to_string(),
                value: "old@example.com".to_string(),
            },
            UserAttribute {
                name: "name".to_string(),
                value: "Alice".to_string(),
            },
        ];
        create(
            &db,
            "us-east-1_testpool",
            "carol",
            None,
            "CONFIRMED",
            &initial,
        )
        .unwrap();

        // Update email and add phone
        let new_attrs = vec![
            UserAttribute {
                name: "email".to_string(),
                value: "new@example.com".to_string(),
            },
            UserAttribute {
                name: "phone".to_string(),
                value: "+1234567890".to_string(),
            },
        ];
        let updated =
            update_attributes(&db, "us-east-1_testpool", "carol", &new_attrs).unwrap();
        assert!(updated);

        let user = get(&db, "us-east-1_testpool", "carol").unwrap().unwrap();
        assert_eq!(user.attributes.len(), 3);

        let email = user.attributes.iter().find(|a| a.name == "email").unwrap();
        assert_eq!(email.value, "new@example.com");

        let name = user.attributes.iter().find(|a| a.name == "name").unwrap();
        assert_eq!(name.value, "Alice");

        let phone = user.attributes.iter().find(|a| a.name == "phone").unwrap();
        assert_eq!(phone.value, "+1234567890");
    }
}
