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

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

/// A vault that manages encrypted and decrypted provider configuration files.
pub struct Store {
    workdata_dir: PathBuf,
    /// In-memory password retained after auth so that every save()
    /// can also re-encrypt providers.enc automatically.
    password: RefCell<Option<String>>,
}

impl Store {
    /// Creates a new `Store` rooted at the given `workdata_dir`.
    pub fn new(workdata_dir: &Path) -> Self {
        Self {
            workdata_dir: workdata_dir.to_path_buf(),
            password: RefCell::new(None),
        }
    }

    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")
    }

    /// Returns `true` if the decrypted provider file exists (user is authenticated).
    pub fn is_authenticated(&self) -> bool {
        self.decrypted_path().exists()
    }

    /// Returns `true` if the encrypted provider file exists on disk.
    pub fn has_encrypted(&self) -> bool {
        self.encrypted_path().exists()
    }

    /// Initializes a new vault with the given `password`, creating both encrypted and decrypted
    /// provider files. Returns an error if an encrypted file already 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(),
            ));
        }
        self.password.replace(Some(password.to_string()));
        let store = ProviderStore::default();
        self.save_encrypted(&store, password)?;
        self.save(&store)?;
        Ok(store)
    }

    /// Loads the decrypted provider store from disk.
    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)
    }

    /// Saves the provider store to the decrypted file and re-encrypts the encrypted file if the
    /// password is cached.
    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)?;
        }
        // Automatically re-encrypt if we have the password cached
        if let Some(ref pwd) = *self.password.borrow() {
            let ciphertext = crypto::encrypt(contents.as_bytes(), pwd)?;
            std::fs::write(self.encrypted_path(), ciphertext)?;
        }
        Ok(())
    }

    /// Ensure the encryption password is available. If not cached, prompt
    /// the user on the terminal. Returns true if now available.
    pub fn ensure_password(&self) -> Result<bool> {
        if self.password.borrow().is_some() {
            return Ok(true);
        }
        // Only prompt if the encrypted file exists
        if !self.has_encrypted() {
            return Ok(false);
        }
        let password = rpassword::prompt_password("Password: ")?;
        // Verify the password by attempting to decrypt
        self.load_encrypted(&password)?;
        self.password.replace(Some(password));
        Ok(true)
    }

    /// Encrypts and writes the provider store to the encrypted file with the given `password`.
    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(())
    }

    /// Loads and decrypts the provider store from the encrypted file using the given `password`.
    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)
    }

    /// Authenticates with the given `password`, decrypting the vault, caching the password, and
    /// writing the decrypted provider file.
    pub fn authenticate(&self, password: &str) -> Result<ProviderStore> {
        let store = self.load_encrypted(password)?;
        self.password.replace(Some(password.to_string()));
        self.save(&store)?;
        Ok(store)
    }

    /// Logs out by removing the decrypted provider file and clearing the cached password.
    pub fn logout(&self) -> Result<()> {
        let path = self.decrypted_path();
        if path.exists() {
            std::fs::remove_file(path)?;
        }
        self.password.replace(None);
        Ok(())
    }

    /// Loads the per-agent active provider state from disk.
    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)
    }

    /// Saves the per-agent active provider state to disk.
    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(())
    }
}
