use argon2::{
    password_hash::{PasswordHasher, PasswordVerifier},
    Argon2,
};
use rouille::{input::json_input, Request, Response};
use serde::Deserialize;
use serde_json::json;
use std::sync::Arc;

use super::helpers::{
    client_ip, get_conn, get_session_cookie, json_error, require_auth, user_agent,
};
use super::AppState;

#[derive(Debug, Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}

pub fn login(request: &Request, state: &Arc<AppState>) -> Response {
    let form: LoginForm = match json_input(request) {
        Ok(f) => f,
        Err(_) => return json_error(400, "Invalid JSON"),
    };

    let mut conn = match get_conn(state) {
        Ok(c) => c,
        Err(e) => return e,
    };

    let user = match conn.query_opt(
        "SELECT id, username, email, role, password_hash, is_active, mfa_enabled, \
         failed_login_attempts, locked_until FROM users WHERE username = $1",
        &[&form.username],
    ) {
        Ok(Some(row)) => {
            let is_active: bool = row.get(5);
            if !is_active {
                return json_error(401, "Invalid credentials");
            }

            let locked_until: Option<chrono::NaiveDateTime> = row.get(8);
            if locked_until.is_some_and(|locked| locked > chrono::Utc::now().naive_utc()) {
                return json_error(423, "Account temporarily locked");
            }

            let password_hash: String = row.get(4);
            let is_valid = match argon2::password_hash::PasswordHash::new(&password_hash) {
                Ok(h) => Argon2::default()
                    .verify_password(form.password.as_bytes(), &h)
                    .is_ok(),
                Err(_) => false,
            };

            if !is_valid {
                let user_id: i32 = row.get(0);
                let mut attempts: i32 = row.get(7);
                attempts += 1;
                let mut new_locked_until = locked_until;

                if attempts >= 5 {
                    new_locked_until = Some(
                        chrono::Utc::now().naive_utc() + chrono::Duration::hours(1),
                    );
                    attempts = 0;
                }

                let _ = conn.execute(
                    "UPDATE users SET failed_login_attempts = $1, locked_until = $2 WHERE id = $3",
                    &[&attempts, &new_locked_until, &user_id],
                );
                return json_error(401, "Invalid credentials");
            }

            (
                row.get::<_, i32>(0),
                row.get::<_, String>(1),
                row.get::<_, String>(2),
                row.get::<_, String>(3),
            )
        }
        Ok(None) => return json_error(401, "Invalid credentials"),
        Err(_) => return json_error(500, "Database error"),
    };

    let (user_id, username, email, role) = user;
    let session_id = uuid::Uuid::new_v4().to_string();
    let expires_at = chrono::Utc::now().naive_utc() + chrono::Duration::seconds(1800);

    let _ = conn.execute(
        "INSERT INTO sessions (id, user_id, expires_at, user_agent) VALUES ($1, $2, $3, $4)",
        &[&session_id, &user_id, &expires_at, &"unknown"],
    );
    let _ = conn.execute(
        "UPDATE users SET failed_login_attempts = 0, locked_until = NULL, \
         last_login = CURRENT_TIMESTAMP WHERE id = $1",
        &[&user_id],
    );

    let ip = client_ip(request);
    let ua = user_agent(request);
    let _ = conn.execute(
        "INSERT INTO session_audit_log (user_id, session_id, event, ip_address, user_agent, metadata) \
         VALUES ($1, $2, 'login', $3, $4, '{}'::jsonb)",
        &[&user_id, &session_id, &ip, &ua],
    );

    Response::json(&json!({
        "user": { "id": user_id, "username": username, "email": email, "role": role },
        "session_id": session_id
    }))
    .with_additional_header(
        "Set-Cookie",
        format!(
            "session_id={}; Path=/; HttpOnly; SameSite=Strict; Max-Age=1800",
            session_id
        ),
    )
}

pub fn logout(request: &Request, state: &Arc<AppState>) -> Response {
    let session_cookie = match get_session_cookie(request) {
        Some(c) => c,
        None => return json_error(401, "Authentication required"),
    };

    let mut conn = match get_conn(state) {
        Ok(c) => c,
        Err(e) => return e,
    };

    if let Ok(Some(row)) = conn.query_opt(
        "SELECT user_id FROM sessions WHERE id = $1",
        &[&session_cookie],
    ) {
        let uid: i32 = row.get(0);
        let ip = client_ip(request);
        let ua = user_agent(request);
        let _ = conn.execute(
            "INSERT INTO session_audit_log (user_id, session_id, event, ip_address, user_agent, metadata) \
             VALUES ($1, $2, 'logout', $3, $4, '{}'::jsonb)",
            &[&uid, &session_cookie, &ip, &ua],
        );
    }

    let _ = conn.execute("DELETE FROM sessions WHERE id = $1", &[&session_cookie]);

    Response::json(&json!({"message": "Logged out"})).with_additional_header(
        "Set-Cookie",
        "session_id=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0",
    )
}

pub fn me(request: &Request, state: &Arc<AppState>) -> Response {
    let mut conn = match get_conn(state) {
        Ok(c) => c,
        Err(e) => return e,
    };
    match require_auth(request, &mut conn) {
        Ok(user) => Response::json(&json!({
            "id": user.id, "username": user.username,
            "email": user.email, "role": user.role,
        })),
        Err(e) => e,
    }
}

pub fn change_password(request: &Request, state: &Arc<AppState>) -> Response {
    let mut conn = match get_conn(state) {
        Ok(c) => c,
        Err(e) => return e,
    };
    let user = match require_auth(request, &mut conn) {
        Ok(u) => u,
        Err(e) => return e,
    };

    let form: sap_sync_backend::models::PasswordChangeForm = match json_input(request) {
        Ok(f) => f,
        Err(_) => return json_error(400, "Invalid JSON"),
    };

    let current_hash: String = match conn.query_opt(
        "SELECT password_hash FROM users WHERE id = $1",
        &[&user.id],
    ) {
        Ok(Some(row)) => row.get(0),
        _ => return json_error(500, "Database error"),
    };

    let is_valid = match argon2::password_hash::PasswordHash::new(&current_hash) {
        Ok(h) => Argon2::default()
            .verify_password(form.current_password.as_bytes(), &h)
            .is_ok(),
        Err(_) => false,
    };
    if !is_valid {
        return json_error(401, "Current password is incorrect");
    }

    let salt = argon2::password_hash::SaltString::generate(rand::thread_rng());
    let new_hash = match Argon2::default().hash_password(form.new_password.as_bytes(), &salt) {
        Ok(h) => h.to_string(),
        Err(_) => return json_error(500, "Failed to hash password"),
    };

    let _ = conn.execute(
        "UPDATE users SET password_hash = $1 WHERE id = $2",
        &[&new_hash, &user.id],
    );
    Response::json(&json!({"message": "Password changed successfully"}))
}

pub fn mfa_setup(request: &Request, state: &Arc<AppState>) -> Response {
    let mut conn = match get_conn(state) {
        Ok(c) => c,
        Err(e) => return e,
    };
    let user = match require_auth(request, &mut conn) {
        Ok(u) => u,
        Err(e) => return e,
    };

    use rand::RngCore;
    let mut secret_bytes = [0u8; 20];
    rand::thread_rng().fill_bytes(&mut secret_bytes);
    let secret_b32 = super::base32_encode(&secret_bytes);

    let _ = conn.execute(
        "UPDATE users SET mfa_secret = $1 WHERE id = $2",
        &[&secret_b32, &user.id],
    );

    // Generate and store hashed backup codes
    let mut backup_codes: Vec<String> = Vec::new();
    for _ in 0..8 {
        let code = format!("{:08}", rand::thread_rng().next_u32() % 100_000_000);
        let salt = argon2::password_hash::SaltString::generate(&mut rand::thread_rng());
        if let Ok(hash) = Argon2::default().hash_password(code.as_bytes(), &salt) {
            let _ = conn.execute(
                "INSERT INTO mfa_backup_codes (user_id, code_hash) VALUES ($1, $2)",
                &[&user.id, &hash.to_string()],
            );
        }
        backup_codes.push(code);
    }

    let qr_url = format!(
        "otpauth://totp/SAP-PLEX-SYNC:{}?secret={}&issuer=SAP-PLEX-SYNC&digits=6&period=30",
        user.username, secret_b32
    );

    Response::json(&json!({
        "method": "totp",
        "secret": secret_b32,
        "qr_code_url": qr_url,
        "backup_codes": backup_codes,
    }))
}

pub fn mfa_verify(request: &Request, state: &Arc<AppState>) -> Response {
    #[derive(Deserialize)]
    struct MfaVerifyForm {
        code: String,
    }

    let form: MfaVerifyForm = match json_input(request) {
        Ok(f) => f,
        Err(_) => return json_error(400, "Invalid JSON"),
    };

    let mut conn = match get_conn(state) {
        Ok(c) => c,
        Err(e) => return e,
    };

    let session_cookie = match get_session_cookie(request) {
        Some(c) => c,
        None => return json_error(401, "Not authenticated"),
    };

    let row = match conn.query_opt(
        "SELECT u.id, u.mfa_secret FROM users u \
         JOIN sessions s ON u.id = s.user_id \
         WHERE s.id = $1 AND s.expires_at > CURRENT_TIMESTAMP",
        &[&session_cookie],
    ) {
        Ok(Some(r)) => r,
        Ok(None) => return json_error(401, "Invalid session"),
        Err(_) => return json_error(500, "Database error"),
    };

    let user_id: i32 = row.get("id");
    let mfa_secret: Option<String> = row.get("mfa_secret");

    let secret = match mfa_secret {
        Some(s) => s,
        None => return json_error(400, "MFA not set up"),
    };

    let secret_bytes = match super::base32_decode(&secret) {
        Some(b) => b,
        None => return json_error(500, "Invalid MFA secret"),
    };

    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();
    let expected = totp_lite::totp_custom::<totp_lite::Sha1>(30, 6, &secret_bytes, now);

    if form.code == expected {
        let _ = conn.execute(
            "UPDATE users SET mfa_enabled = TRUE WHERE id = $1",
            &[&user_id],
        );
        Response::json(&json!({"message": "MFA enabled successfully"}))
    } else {
        json_error(400, "Invalid verification code")
    }
}
