use std::path::Path;
use std::process::Command;

use crate::error::{ConfigError, Result};

/// SOPS (Secrets Operations) integration for decrypting encrypted configs
#[derive(Debug, Clone)]
pub struct SopsDecryptor {
    sops_executable: String,
}

impl SopsDecryptor {
    /// Create a new SOPS decryptor
    pub fn new() -> Result<Self> {
        // Check if sops is installed
        match Command::new("sops").arg("--version").output() {
            Ok(output) if output.status.success() => {
                let version = String::from_utf8_lossy(&output.stdout);
                tracing::debug!("SOPS available: {}", version.trim());
                Ok(Self {
                    sops_executable: "sops".to_string(),
                })
            }
            _ => Err(ConfigError::io_error(
                "SOPS executable not found. Install SOPS: https://github.com/mozilla/sops",
            )),
        }
    }

    /// Check if SOPS is available
    pub fn is_available() -> bool {
        Command::new("sops")
            .arg("--version")
            .output()
            .map(|output| output.status.success())
            .unwrap_or(false)
    }

    /// Decrypt a SOPS-encrypted file and return the plaintext content
    pub fn decrypt_file<P: AsRef<Path>>(&self, path: P) -> Result<String> {
        let path = path.as_ref();

        if !path.exists() {
            return Err(ConfigError::not_found(format!(
                "SOPS file not found: {:?}",
                path
            )));
        }

        tracing::debug!("Decrypting SOPS file: {:?}", path);

        match Command::new(&self.sops_executable)
            .arg("--decrypt")
            .arg(path)
            .output()
        {
            Ok(output) => {
                if !output.status.success() {
                    let stderr = String::from_utf8_lossy(&output.stderr);
                    return Err(ConfigError::validation_failed(format!(
                        "SOPS decryption failed: {}",
                        stderr
                    )));
                }

                let content = String::from_utf8(output.stdout).map_err(|e| {
                    ConfigError::deserialization_failed(format!(
                        "Invalid UTF-8 in SOPS output: {}",
                        e
                    ))
                })?;

                tracing::debug!("Successfully decrypted SOPS file");
                Ok(content)
            }
            Err(e) => Err(ConfigError::io_error(format!(
                "Failed to execute SOPS: {}",
                e
            ))),
        }
    }

    /// Try to find and decrypt a secrets file for a service
    /// Looks for patterns: {service}.secrets.ncl.sops,
    /// {service}.secrets.toml.sops
    pub fn find_and_decrypt_secrets(
        &self,
        service_name: &str,
        search_dir: &Path,
    ) -> Option<String> {
        let patterns = vec![
            format!("{}.secrets.ncl.sops", service_name),
            format!("{}.secrets.toml.sops", service_name),
            format!("secrets.{}.ncl.sops", service_name),
            format!("secrets.{}.toml.sops", service_name),
        ];

        for pattern in patterns {
            let secret_file = search_dir.join(&pattern);
            if secret_file.exists() {
                match self.decrypt_file(&secret_file) {
                    Ok(content) => {
                        tracing::info!("Loaded secrets for {}: {}", service_name, pattern);
                        return Some(content);
                    }
                    Err(e) => {
                        tracing::warn!("Failed to decrypt {}: {}", pattern, e);
                    }
                }
            }
        }

        None
    }

    /// Check if a file appears to be SOPS-encrypted
    pub fn is_sops_encrypted(path: &Path) -> bool {
        path.extension()
            .and_then(|ext| ext.to_str())
            .map(|ext| ext == "sops")
            .unwrap_or(false)
    }

    /// Get the original file format from a SOPS filename
    /// Example: "config.ncl.sops" → "ncl"
    pub fn get_original_format(sops_path: &Path) -> Option<String> {
        sops_path
            .file_stem()
            .and_then(|stem| stem.to_str())
            .and_then(|stem_str| stem_str.split('.').next_back().map(|ext| ext.to_string()))
    }
}

impl Default for SopsDecryptor {
    fn default() -> Self {
        Self::new().unwrap_or_else(|_| Self {
            sops_executable: "sops".to_string(),
        })
    }
}

#[cfg(test)]
mod tests {
    use tempfile::TempDir;

    use super::*;

    #[test]
    fn test_sops_availability() {
        // This test checks if SOPS is installed
        let available = SopsDecryptor::is_available();
        if available {
            println!("✓ SOPS is available");
        } else {
            println!("ℹ SOPS not installed (expected in CI)");
        }
    }

    #[test]
    fn test_sops_decryptor_new() {
        match SopsDecryptor::new() {
            Ok(decryptor) => {
                assert_eq!(decryptor.sops_executable, "sops");
                println!("✓ SOPS decryptor created");
            }
            Err(e) => {
                println!("ℹ SOPS not available: {}", e);
            }
        }
    }

    #[test]
    fn test_is_sops_encrypted() {
        assert!(SopsDecryptor::is_sops_encrypted(Path::new(
            "config.ncl.sops"
        )));
        assert!(SopsDecryptor::is_sops_encrypted(Path::new(
            "secrets.toml.sops"
        )));
        assert!(!SopsDecryptor::is_sops_encrypted(Path::new("config.ncl")));
        assert!(!SopsDecryptor::is_sops_encrypted(Path::new("secrets.toml")));
    }

    #[test]
    fn test_get_original_format() {
        assert_eq!(
            SopsDecryptor::get_original_format(Path::new("config.ncl.sops")),
            Some("ncl".to_string())
        );
        assert_eq!(
            SopsDecryptor::get_original_format(Path::new("secrets.toml.sops")),
            Some("toml".to_string())
        );
        assert_eq!(
            SopsDecryptor::get_original_format(Path::new("config.yaml.sops")),
            Some("yaml".to_string())
        );
    }

    #[test]
    fn test_find_and_decrypt_secrets_missing() {
        let temp_dir = TempDir::new().unwrap();
        match SopsDecryptor::new() {
            Ok(decryptor) => {
                let result = decryptor.find_and_decrypt_secrets("vault-service", temp_dir.path());
                assert!(result.is_none(), "Should not find any secrets");
                println!("✓ Missing secrets handled correctly");
            }
            Err(_) => {
                println!("ℹ SOPS not available, skipping decrypt test");
            }
        }
    }
}
