use anyhow::Result;
use clap::Parser;
use rusqlite::Connection;
use std::path::PathBuf;
use std::process::Command;

#[derive(Debug, Clone)]
pub struct IscsiConfig {
    pub raid_device: String,
    pub target_iqn: String,
    pub portal_ip: String,
    pub portal_port: u16,
    pub db_path: PathBuf,
}

impl IscsiConfig {
    pub fn new(user_id: &str) -> Self {
        let raid_device = format!("/dev/mapper/markbase_{}", user_id);
        let target_iqn = format!("iqn.2026-05.momentry:markbase_{}", user_id);
        let db_path = PathBuf::from(format!("data/users/{}/{}.sqlite", user_id, user_id));
        
        Self {
            raid_device,
            target_iqn,
            portal_ip: "0.0.0.0".to_string(),
            portal_port: 3260,
            db_path,
        }
    }
    
    pub fn create_raid5(&self, disks: &[String], stripe_size_kb: u64) -> Result<()> {
        if disks.len() < 3 {
            return Err(anyhow::anyhow!("RAID5 requires at least 3 disks"));
        }
        
        let _disk_args: Vec<String> = disks.iter().map(|d| d.clone()).collect();
        
        let output = Command::new("dmsetup")
            .arg("create")
            .arg(&self.raid_device.replace("/dev/mapper/", ""))
            .arg("--table")
            .arg(format!(
                "0 {} raid raid5 {} {} region_size {}",
                get_disk_size(&disks[0])?,
                disks.len(),
                stripe_size_kb * 1024,
                stripe_size_kb
            ))
            .output()?;
        
        if !output.status.success() {
            return Err(anyhow::anyhow!("dmsetup failed: {}", 
                String::from_utf8_lossy(&output.stderr)));
        }
        
        println!("RAID5 created: {}", self.raid_device);
        Ok(())
    }
    
    pub fn verify_raid(&self) -> Result<String> {
        let output = Command::new("dmsetup")
            .arg("status")
            .arg(&self.raid_device.replace("/dev/mapper/", ""))
            .output()?;
        
        let status = String::from_utf8_lossy(&output.stdout).trim().to_string();
        println!("RAID5 status: {}", status);
        Ok(status)
    }
    
    pub fn create_iscsi_target(&self) -> Result<()> {
        let targetcli_commands = [
            format!("cd backstores/block"),
            format!("create name={} dev={}", 
                self.raid_device.replace("/dev/mapper/", "markbase_"),
                self.raid_device),
            format!("cd /iscsi"),
            format!("create {}", self.target_iqn),
            format!("cd {}/{}/tpg1/luns", "/iscsi", self.target_iqn),
            format!("create /backstores/block/markbase_{}", 
                self.raid_device.replace("/dev/mapper/", "")),
            format!("cd {}/{}/tpg1/portals", "/iscsi", self.target_iqn),
            format!("create {} {}", self.portal_ip, self.portal_port),
        ];
        
        for cmd in &targetcli_commands {
            let output = Command::new("targetcli")
                .arg(cmd)
                .output()?;
            
            if !output.status.success() {
                return Err(anyhow::anyhow!("targetcli failed: {}", 
                    String::from_utf8_lossy(&output.stderr)));
            }
        }
        
        println!("iSCSI Target created: {}", self.target_iqn);
        println!("Portal: {}:{}", self.portal_ip, self.portal_port);
        Ok(())
    }
    
    pub fn init_db(&self) -> Result<()> {
        if !self.db_path.exists() {
            std::fs::create_dir_all(self.db_path.parent().unwrap())?;
            
            let conn = Connection::open(&self.db_path)?;
            conn.execute(
                "CREATE TABLE IF NOT EXISTS lun_mapping (
                    lun INTEGER PRIMARY KEY,
                    node_id TEXT NOT NULL,
                    created_at INTEGER DEFAULT (strftime('%s', 'now'))
                )",
                [],
            )?;
            
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_node_id ON lun_mapping(node_id)",
                [],
            )?;
            
            println!("Database created: {}", self.db_path.display());
        } else {
            println!("Database exists: {}", self.db_path.display());
        }
        
        Ok(())
    }
    
    pub fn map_lun_to_sqlite(&self, lun: u64, node_id: &str) -> Result<()> {
        let conn = Connection::open(&self.db_path)?;
        
        conn.execute(
            "INSERT OR REPLACE INTO lun_mapping (lun, node_id) VALUES (?1, ?2)",
            rusqlite::params![lun, node_id],
        )?;
        
        println!("Mapped LUN {} -> node_id {}", lun, node_id);
        Ok(())
    }
    
    pub fn get_node_id_for_lun(&self, lun: u64) -> Result<Option<String>> {
        let conn = Connection::open(&self.db_path)?;
        
        let result = conn.query_row(
            "SELECT node_id FROM lun_mapping WHERE lun = ?1",
            rusqlite::params![lun],
            |row| row.get::<_, String>(0),
        );
        
        match result {
            Ok(node_id) => Ok(Some(node_id)),
            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
            Err(e) => Err(anyhow::anyhow!("Database error: {}", e)),
        }
    }
}

fn get_disk_size(disk_path: &str) -> Result<u64> {
    let output = Command::new("blockdev")
        .arg("--getsize64")
        .arg(disk_path)
        .output()?;
    
    let size_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
    let size: u64 = size_str.parse()?;
    Ok(size)
}

#[derive(Parser, Debug)]
#[command(name = "configure_iscsi", about = "MarkBase iSCSI configuration tool")]
struct Opt {
    #[arg(help = "User ID")]
    user_id: String,
    
    #[arg(long, help = "Disks for RAID5 (minimum 3)")]
    disks: Vec<String>,
    
    #[arg(long, default_value = "64", help = "Stripe size in KB")]
    stripe_size: u64,
    
    #[arg(long, help = "Verify existing configuration")]
    verify: bool,
    
    #[arg(long, help = "Create iSCSI target only")]
    create_target: bool,
}

fn main() -> Result<()> {
    let opt = Opt::parse();
    let config = IscsiConfig::new(&opt.user_id);
    
    println!("=== MarkBase iSCSI Configuration ===");
    println!("User ID: {}", opt.user_id);
    
    if opt.verify {
        println!("Verifying existing configuration...");
        config.verify_raid()?;
        println!("Configuration verified successfully");
        return Ok(());
    }
    
    if !opt.disks.is_empty() {
        println!("Creating RAID5 with disks: {:?}", opt.disks);
        config.create_raid5(&opt.disks, opt.stripe_size)?;
        config.verify_raid()?;
    }
    
    if opt.create_target {
        println!("Creating iSCSI target...");
        config.create_iscsi_target()?;
    }
    
    config.init_db()?;
    
    println!("=== Configuration Complete ===");
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    use rusqlite::Connection;

    #[test]
    fn test_iscsi_config_creation() {
        let config = IscsiConfig::new("test_user");
        
        assert_eq!(config.raid_device, "/dev/mapper/markbase_test_user");
        assert_eq!(config.target_iqn, "iqn.2026-05.momentry:markbase_test_user");
        assert_eq!(config.portal_ip, "0.0.0.0");
        assert_eq!(config.portal_port, 3260);
    }

    #[test]
    fn test_db_path_format() {
        let config = IscsiConfig::new("warren");
        assert!(config.db_path.to_str().unwrap().contains("warren.sqlite"));
    }

    #[test]
    fn test_target_iqn_format() {
        let user_ids = ["warren", "momentry", "demo"];
        
        for user_id in user_ids {
            let config = IscsiConfig::new(user_id);
            assert!(config.target_iqn.starts_with("iqn.2026-05.momentry:markbase_"));
            assert!(config.target_iqn.ends_with(user_id));
        }
    }

    #[test]
    fn test_raid_device_format() {
        let config = IscsiConfig::new("test123");
        assert!(config.raid_device.starts_with("/dev/mapper/"));
        assert!(config.raid_device.ends_with("test123"));
    }

    #[test]
    fn test_sqlite_lun_mapping() {
        let temp_dir = TempDir::new().unwrap();
        let db_path = temp_dir.path().join("test.sqlite");
        
        let config = IscsiConfig::new("test_user");
        let config_with_db = IscsiConfig {
            db_path: db_path.clone(),
            ..config
        };
        
        config_with_db.init_db().unwrap();
        let result = config_with_db.map_lun_to_sqlite(1, "node_abc123");
        assert!(result.is_ok());
        
        let conn = Connection::open(&db_path).unwrap();
        let node_id: String = conn.query_row(
            "SELECT node_id FROM lun_mapping WHERE lun = 1",
            [],
            |row| row.get(0)
        ).unwrap();
        
        assert_eq!(node_id, "node_abc123");
    }

    #[test]
    fn test_multiple_lun_mappings() {
        let temp_dir = TempDir::new().unwrap();
        let db_path = temp_dir.path().join("test_multi.sqlite");
        
        let config = IscsiConfig::new("test_user");
        let config_with_db = IscsiConfig {
            db_path: db_path.clone(),
            ..config
        };
        
        config_with_db.init_db().unwrap();
        
        for i in 1..=10 {
            let result = config_with_db.map_lun_to_sqlite(i, &format!("node_{}", i));
            assert!(result.is_ok());
        }
        
        let conn = Connection::open(&db_path).unwrap();
        let count: i64 = conn.query_row(
            "SELECT COUNT(*) FROM lun_mapping",
            [],
            |row| row.get(0)
        ).unwrap();
        
        assert_eq!(count, 10);
    }
}