use rand::Rng;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use totp_lite::{totp_custom, Sha1};

#[derive(Debug, Deserialize)]
pub struct MfaSetupRequest {
    pub method: String,
}

#[derive(Debug, Serialize)]
pub struct MfaSetupResponse {
    pub method: String,
    pub secret: String,
    pub qr_code_url: Option<String>,
    pub backup_codes: Vec<String>,
    pub test_code: Option<String>,
}

pub fn generate_totp_secret() -> String {
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    let mut secret = String::with_capacity(16);
    let mut rng = rand::thread_rng();
    for _ in 0..16 {
        let idx = rng.gen_range(0..CHARSET.len());
        secret.push(CHARSET[idx] as char);
    }
    secret
}

pub fn generate_qr_code_url(secret: &str, issuer: &str, account: &str) -> String {
    format!(
        "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={}",
        urlencoding::encode(&format!(
            "otpauth://totp/{}:{}?secret={}&issuer={}&algorithm=SHA1&digits=6&period=30",
            urlencoding::encode(issuer),
            urlencoding::encode(account),
            secret,
            urlencoding::encode(issuer)
        ))
    )
}

pub fn verify_totp(secret: &str, code: &str) -> Result<usize, &'static str> {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();
    let expected = totp_custom::<Sha1>(30, 6, secret.as_bytes(), now);
    if expected == code {
        Ok(0)
    } else {
        Err("Invalid code")
    }
}

pub fn generate_backup_codes() -> Vec<String> {
    (0..8)
        .map(|_| {
            const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
            let mut code = String::with_capacity(8);
            let mut rng = rand::thread_rng();
            for _ in 0..8 {
                let idx = rng.gen_range(0..CHARSET.len());
                code.push(CHARSET[idx] as char);
            }
            format!("{}-{}", &code[0..4], &code[4..8])
        })
        .collect()
}

pub fn hash_backup_code(code: &str) -> String {
    use sha2::{Digest, Sha256};
    let mut hasher = Sha256::new();
    hasher.update(code.as_bytes());
    hex::encode(hasher.finalize())
}

#[derive(Debug, Deserialize)]
pub struct EmailOtpRequest {
    pub email: String,
    pub code: String,
}
