use std::collections::HashMap;
use std::path::{Path, PathBuf};

use super::crypto;
use super::error::{ClosureError, Result};
use super::provider::ProviderStore;

pub struct Store {
    workdata_dir: PathBuf,
}

impl Store {
    pub fn new(workdata_dir: &Path) -> Self {
        Self {
            workdata_dir: workdata_dir.to_path_buf(),
        }
    }

    fn encrypted_path(&self) -> PathBuf {
        self.workdata_dir.join("providers.enc")
    }

    fn decrypted_path(&self) -> PathBuf {
        self.workdata_dir.join("providers.json")
    }

    fn state_path(&self) -> PathBuf {
        self.workdata_dir.join("state.json")
    }

    pub fn is_authenticated(&self) -> bool {
        self.decrypted_path().exists()
    }

    pub fn has_encrypted(&self) -> bool {
        self.encrypted_path().exists()
    }

    pub fn init(&self, password: &str) -> Result<ProviderStore> {
        if self.has_encrypted() {
            return Err(ClosureError::Crypto(
                "vault already exists — providers.enc already present. Use `closure auth` to unlock."
                    .to_string(),
            ));
        }
        let store = ProviderStore::default();
        self.save_encrypted(&store, password)?;
        self.save(&store)?;
        Ok(store)
    }

    pub fn load(&self) -> Result<ProviderStore> {
        let path = self.decrypted_path();
        if !path.exists() {
            return Err(ClosureError::AuthRequired);
        }
        let contents = std::fs::read_to_string(&path)?;
        let store = serde_json::from_str(&contents)?;
        Ok(store)
    }

    pub fn save(&self, store: &ProviderStore) -> Result<()> {
        let contents = serde_json::to_string_pretty(store)?;
        std::fs::write(self.decrypted_path(), contents)?;
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perms = std::fs::metadata(self.decrypted_path())?.permissions();
            perms.set_mode(0o600);
            std::fs::set_permissions(self.decrypted_path(), perms)?;
        }
        Ok(())
    }

    pub fn save_encrypted(&self, store: &ProviderStore, password: &str) -> Result<()> {
        let plaintext = serde_json::to_vec(store)?;
        let ciphertext = crypto::encrypt(&plaintext, password)?;
        std::fs::write(self.encrypted_path(), ciphertext)?;
        Ok(())
    }

    pub fn load_encrypted(&self, password: &str) -> Result<ProviderStore> {
        let ciphertext = std::fs::read(self.encrypted_path())?;
        let plaintext = crypto::decrypt(&ciphertext, password)?;
        let store = serde_json::from_slice(&plaintext)?;
        Ok(store)
    }

    pub fn authenticate(&self, password: &str) -> Result<ProviderStore> {
        let store = self.load_encrypted(password)?;
        self.save(&store)?;
        Ok(store)
    }

    pub fn logout(&self) -> Result<()> {
        let path = self.decrypted_path();
        if path.exists() {
            std::fs::remove_file(path)?;
        }
        Ok(())
    }

    pub fn load_state(&self) -> Result<HashMap<String, Option<String>>> {
        let path = self.state_path();
        if !path.exists() {
            return Ok(HashMap::new());
        }
        let contents = std::fs::read_to_string(&path)?;
        let state: HashMap<String, Option<String>> = serde_json::from_str(&contents)?;
        Ok(state)
    }

    pub fn save_state(&self, state: &HashMap<String, Option<String>>) -> Result<()> {
        let contents = serde_json::to_string_pretty(state)?;
        std::fs::write(self.state_path(), contents)?;
        Ok(())
    }
}
