use std::path::Path;
use std::path::PathBuf;
use std::sync::Mutex;

use sequoia_cryptoki::PublicKeyAlgorithm;

use super::Backend;
use super::Result;

pub struct KryopticPqc {
    /// The kryoptic configuration directory.  This is where we store
    /// the configuration file and any token databases.
    kryoptic: PathBuf,

    /// The environment variables to set.
    env: Vec<(String, String)>,

    /// The next free slot.
    next_slot: Mutex<u64>,
    /// The last free slot.
    last_slot: u64,
}

impl KryopticPqc {
    pub fn new(working_dir: &Path) -> Result<Box<dyn Backend>> {
        let kryoptic = working_dir.join("kryoptic");
        std::fs::create_dir_all(&kryoptic)
            .expect("can create kryoptic directory");

        let slots: usize = 10;
        let config = (0..slots)
            .map(|i| {
                format!("[[slots]]
slot = {}
dbtype = \"sqlite\"
dbargs = \"{}\"
",
                        i,
                        kryoptic.join(format!("t{}.sqlite", i)).display())
            })
            .collect::<Vec<String>>()
            .join("\n")
            .into_bytes();

        let config_file = kryoptic.join("token.conf");

        std::fs::write(&config_file, &config)
            .expect("can create configuration file");

        Ok(Box::new(KryopticPqc {
            kryoptic,
            env: vec![
                ("KRYOPTIC_CONF".to_string(),
                 config_file.display().to_string())
            ],
            next_slot: Mutex::new(0),
            last_slot: slots as u64,
        }))
    }
}

impl Backend for KryopticPqc {
    fn env(&self) -> &[(String, String)] {
        &self.env[..]
    }

    fn args(&self) -> &[String] {
        &[]
    }

    fn algo_supported(&self, algo: &PublicKeyAlgorithm) -> bool {
        use PublicKeyAlgorithm::*;
        match algo {
            RSA => true,
            Cv25519 => true,
            X25519 => true,
            X448 => true,
            EdDSA => true,
            Ed25519 => true,
            Ed448 => true,
            MLDSA65_Ed25519 => true,
            MLDSA87_Ed448 => true,
            SLHDSA128s => true,
            SLHDSA128f => true,
            SLHDSA256s => true,
            MLKEM768_X25519 => true,
            MLKEM1024_X448 => true,
            algo => unreachable!("Missing algorithm: {}", algo),
        }
    }

    fn allocate_slot(&self) -> u64 {
        let mut next_slot = self.next_slot.lock().unwrap();

        if *next_slot >= self.last_slot {
            panic!("Test HSM is out of free slots.");
        } else {
            let slot = *next_slot;
            eprintln!("Allocating slot {}", slot);
            *next_slot += 1;
            slot
        }
    }
}

pub struct KryopticNoPqc {
    /// The kryoptic configuration directory.  This is where we store
    /// the configuration file and any token databases.
    kryoptic: PathBuf,

    /// The environment variables to set.
    env: Vec<(String, String)>,

    /// The next free slot.
    next_slot: Mutex<u64>,
    /// The last free slot.
    last_slot: u64,
}

impl KryopticNoPqc {
    pub fn new(working_dir: &Path) -> Result<Box<dyn Backend>> {
        let kryoptic = working_dir.join("kryoptic");
        std::fs::create_dir_all(&kryoptic)
            .expect("can create kryoptic directory");

        let slots: usize = 10;
        let config = (0..slots)
            .map(|i| {
                format!("[[slots]]
slot = {}
dbtype = \"sqlite\"
dbargs = \"{}\"
",
                        i,
                        kryoptic.join(format!("t{}.sqlite", i)).display())
            })
            .collect::<Vec<String>>()
            .join("\n")
            .into_bytes();

        let config_file = kryoptic.join("token.conf");

        std::fs::write(&config_file, &config)
            .expect("can create configuration file");

        Ok(Box::new(KryopticNoPqc {
            kryoptic,
            env: vec![
                ("KRYOPTIC_CONF".to_string(),
                 config_file.display().to_string())
            ],
            next_slot: Mutex::new(0),
            last_slot: slots as u64,
        }))
    }
}

impl Backend for KryopticNoPqc {
    fn env(&self) -> &[(String, String)] {
        &self.env[..]
    }

    fn args(&self) -> &[String] {
        &[]
    }

    fn algo_supported(&self, algo: &PublicKeyAlgorithm) -> bool {
        use PublicKeyAlgorithm::*;
        match algo {
            RSA => true,
            Cv25519 => true,
            X25519 => true,
            X448 => true,
            EdDSA => true,
            Ed25519 => true,
            Ed448 => true,
            MLDSA65_Ed25519 => false,
            MLDSA87_Ed448 => false,
            SLHDSA128s => false,
            SLHDSA128f => false,
            SLHDSA256s => false,
            MLKEM768_X25519 => false,
            MLKEM1024_X448 => false,
            algo => unreachable!("Missing algorithm: {}", algo),
        }
    }

    fn allocate_slot(&self) -> u64 {
        let mut next_slot = self.next_slot.lock().unwrap();

        if *next_slot >= self.last_slot {
            panic!("Test HSM is out of free slots.");
        } else {
            let slot = *next_slot;
            eprintln!("Allocating slot {}", slot);
            *next_slot += 1;
            slot
        }
    }
}
