use crate::common::{CrylError, CrylResult};
use std::path::Path;

/// Vault file exporter - exports a single file to a Vault KV path
pub fn export_vault_file(path: &str, file: &Path) -> CrylResult<()> {
  // Trim trailing slashes
  let trimmed_path = path.trim_end_matches('/');

  // Extract last component now to exit early
  // and because the file might be under a subdirectory
  let Some(last_component) = file
    .components()
    .next_back()
    .and_then(|component| component.as_os_str().to_str())
    .to_owned()
  else {
    return Err(CrylError::Import {
      importer: "vault".to_string(),
      message: "Path is empty".to_string(),
    });
  };

  // Check if source file exists
  let file_path = Path::new(file);
  if !file_path.exists() {
    return Err(CrylError::Export {
      exporter: "vault-file".to_string(),
      message: format!("Source file not found: {:?}", file),
    });
  }

  // Read file content
  let content = std::fs::read_to_string(file_path)?;

  // Execute vault kv put
  let output = std::process::Command::new("vault")
    .arg("kv")
    .arg("put")
    .arg(trimmed_path)
    .arg(format!("{}={}", last_component, content))
    .output()
    .map_err(|e| CrylError::Export {
      exporter: "vault-file".to_string(),
      message: format!("Failed to execute vault kv put: {}", e),
    })?;

  if !output.status.success() {
    return Err(CrylError::Export {
      exporter: "vault-file".to_string(),
      message: format!(
        "vault kv put failed with status: {}\nstderr: {}",
        output.status,
        String::from_utf8_lossy(&output.stderr)
      ),
    });
  }

  Ok(())
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::common::vault_container;
  use serial_test::serial;
  use std::process::Command;
  use tempfile::TempDir;

  #[tokio::test]
  #[serial(environment)]
  async fn test_export_vault_file_success() -> anyhow::Result<()> {
    let _container = vault_container("vfile-export-test").await?;

    let temp_dir = TempDir::new()?;
    let file_path = temp_dir.path().join("secret.txt");

    // Create test file
    std::fs::write(&file_path, "my-secret-value")?;

    // Export to vault
    export_vault_file("kv/my-app", &file_path)?;

    // Verify using vault CLI
    let output = Command::new("vault")
      .args(["kv", "get", "-format=json", "kv/my-app"])
      .output()?;

    let json: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    assert_eq!(json["data"]["data"]["secret.txt"], "my-secret-value");

    Ok(())
  }

  #[tokio::test]
  #[serial(environment)]
  async fn test_export_vault_file_missing_source() -> anyhow::Result<()> {
    let _container = vault_container("vfile-export-missing-test").await?;

    let temp_dir = TempDir::new()?;
    let file_path = temp_dir.path().join("secret.txt");

    // Try to export non-existent file
    let result = export_vault_file("kv/my-app", &file_path);
    assert!(result.is_err());

    let err = result.unwrap_err();
    assert!(matches!(err, CrylError::Export { exporter, message: _ }
      if exporter == "vault-file"));

    Ok(())
  }

  #[tokio::test]
  #[serial(environment)]
  async fn test_export_vault_file_nested_path() -> anyhow::Result<()> {
    let _container = vault_container("vfile-export-nested-test").await?;

    let temp_dir = TempDir::new()?;
    let file_name = "secret.txt";
    let file_path = temp_dir.path().join(file_name);
    let file_content = "s3cr3t";

    std::fs::write(&file_path, file_content)?;

    // Export to nested path
    export_vault_file("kv/team/project/env", &file_path)?;

    // Verify
    let output = Command::new("vault")
      .args(["kv", "get", "-format=json", "kv/team/project/env"])
      .output()?;

    let json: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    assert_eq!(json["data"]["data"][file_name], file_content);

    Ok(())
  }

  #[tokio::test]
  #[serial(environment)]
  async fn test_export_vault_file_with_trailing_slash() -> anyhow::Result<()> {
    let _container = vault_container("vfile-export-slash-test").await?;

    let temp_dir = TempDir::new()?;
    let file_name = "secret.txt";
    let file_path = temp_dir.path().join(file_name);
    let file_content = "key: value";

    std::fs::write(&file_path, file_content)?;

    // Path with trailing slash should work
    export_vault_file("kv/my-app/", &file_path)?;

    // Verify
    let output = Command::new("vault")
      .args(["kv", "get", "-format=json", "kv/my-app"])
      .output()?;

    let json: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    assert_eq!(json["data"]["data"][file_name], file_content);

    Ok(())
  }

  #[tokio::test]
  #[serial(environment)]
  async fn test_export_vault_file_overwrites_existing() -> anyhow::Result<()> {
    let _container = vault_container("vfile-export-overwrite-test").await?;

    let temp_dir = TempDir::new()?;
    let file_name = "secret.txt";
    let file_path = temp_dir.path().join(file_name);

    // First export
    std::fs::write(&file_path, "initial")?;
    export_vault_file("kv/overwrite-app", &file_path)?;

    // Verify initial value
    let output = Command::new("vault")
      .args(["kv", "get", "-format=json", "kv/overwrite-app"])
      .output()?;
    let json: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    assert_eq!(json["data"]["data"][file_name], "initial");

    // Update file and re-export
    std::fs::write(&file_path, "updated")?;
    export_vault_file("kv/overwrite-app", &file_path)?;

    // Verify updated value
    let output = Command::new("vault")
      .args(["kv", "get", "-format=json", "kv/overwrite-app"])
      .output()?;
    let json: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    assert_eq!(json["data"]["data"][file_name], "updated");

    Ok(())
  }

  #[tokio::test]
  #[serial(environment)]
  async fn test_export_vault_file_subdir() -> anyhow::Result<()> {
    let _container = vault_container("vfile-export-subdir-test").await?;

    let temp_dir = TempDir::new()?;
    let dir = temp_dir.path().join("subdir");
    let key = "secret.txt";
    let file = dir.join(key);
    let value = "my-secret-value";
    let path = "kv/my-app";

    // Create test file
    std::fs::create_dir_all(file.parent().unwrap()).unwrap();
    std::fs::write(&file, value)?;

    // Export to vault
    export_vault_file(path, &file)?;

    // Verify using vault CLI
    let output = Command::new("vault")
      .args(["kv", "get", "-format=json", path])
      .output()?;

    let json: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    assert_eq!(json["data"]["data"][key], value);

    Ok(())
  }
}
