use std::fs;
use std::path::PathBuf;

use super::database::Database;

/// A sandbox profile that can be applied to an AppImage when launching with Firejail.
#[derive(Debug, Clone)]
pub struct SandboxProfile {
    pub id: Option<i64>,
    pub app_name: String,
    pub profile_version: Option<String>,
    pub author: Option<String>,
    pub description: Option<String>,
    pub content: String,
    pub source: ProfileSource,
}

#[derive(Debug, Clone, PartialEq)]
pub enum ProfileSource {
    Local,
    Community { registry_id: String },
    FirejailDefault,
}

impl ProfileSource {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Local => "local",
            Self::Community { .. } => "community",
            Self::FirejailDefault => "firejail-default",
        }
    }

    pub fn from_record(source: &str, registry_id: Option<&str>) -> Self {
        match source {
            "community" => Self::Community {
                registry_id: registry_id.unwrap_or("").to_string(),
            },
            "firejail-default" => Self::FirejailDefault,
            _ => Self::Local,
        }
    }
}

/// Directory where local sandbox profiles are stored.
fn profiles_dir() -> PathBuf {
    let dir = crate::config::config_dir_fallback()
        .join("driftwood")
        .join("sandbox");
    fs::create_dir_all(&dir).ok();
    dir
}

/// Save a sandbox profile to local storage and the database.
pub fn save_profile(db: &Database, profile: &SandboxProfile) -> Result<PathBuf, SandboxError> {
    let filename = sanitize_profile_name(&profile.app_name);
    let path = profiles_dir().join(format!("{}.profile", filename));

    // Write profile content with metadata header
    let full_content = format_profile_with_header(profile);
    fs::write(&path, &full_content).map_err(|e| SandboxError::Io(e.to_string()))?;

    // Store in database
    db.insert_sandbox_profile(
        &profile.app_name,
        profile.profile_version.as_deref(),
        profile.author.as_deref(),
        profile.description.as_deref(),
        &profile.content,
        profile.source.as_str(),
        match &profile.source {
            ProfileSource::Community { registry_id } => Some(registry_id.as_str()),
            _ => None,
        },
    ).map_err(|e| SandboxError::Database(e.to_string()))?;

    Ok(path)
}

/// Load the most recent sandbox profile for an app from the database.
pub fn load_profile(db: &Database, app_name: &str) -> Result<Option<SandboxProfile>, SandboxError> {
    let record = db.get_sandbox_profile_for_app(app_name)
        .map_err(|e| SandboxError::Database(e.to_string()))?;

    Ok(record.map(|r| SandboxProfile {
        id: Some(r.id),
        app_name: r.app_name,
        profile_version: r.profile_version,
        author: r.author,
        description: r.description,
        content: r.content.clone(),
        source: ProfileSource::from_record(&r.source, r.registry_id.as_deref()),
    }))
}

/// Delete a sandbox profile by ID.
pub fn delete_profile(db: &Database, profile_id: i64) -> Result<(), SandboxError> {
    db.delete_sandbox_profile(profile_id)
        .map_err(|e| SandboxError::Database(e.to_string()))?;
    Ok(())
}

/// List all local sandbox profiles.
pub fn list_profiles(db: &Database) -> Vec<SandboxProfile> {
    let records = db.get_all_sandbox_profiles().unwrap_or_default();
    records.into_iter().map(|r| SandboxProfile {
        id: Some(r.id),
        app_name: r.app_name,
        profile_version: r.profile_version,
        author: r.author,
        description: r.description,
        content: r.content.clone(),
        source: ProfileSource::from_record(&r.source, r.registry_id.as_deref()),
    }).collect()
}

/// Search the community registry for sandbox profiles matching an app name.
/// Uses the GitHub-based registry approach (fetches a JSON index).
#[allow(dead_code)] // Community registry UI not yet wired
pub fn search_community_profiles(registry_url: &str, app_name: &str) -> Result<Vec<CommunityProfileEntry>, SandboxError> {
    let index_url = format!("{}/index.json", registry_url.trim_end_matches('/'));

    let response = ureq::get(&index_url)
        .call()
        .map_err(|e| SandboxError::Network(e.to_string()))?;

    let body = response.into_body().read_to_string()
        .map_err(|e| SandboxError::Network(e.to_string()))?;

    let index: CommunityIndex = serde_json::from_str(&body)
        .map_err(|e| SandboxError::Parse(e.to_string()))?;

    let query = app_name.to_lowercase();
    let matches: Vec<CommunityProfileEntry> = index.profiles
        .into_iter()
        .filter(|p| p.app_name.to_lowercase().contains(&query))
        .collect();

    Ok(matches)
}

/// Download a community profile by its URL and save it locally.
#[allow(dead_code)] // Community registry UI not yet wired
pub fn download_community_profile(
    db: &Database,
    entry: &CommunityProfileEntry,
) -> Result<SandboxProfile, SandboxError> {
    let response = ureq::get(&entry.url)
        .call()
        .map_err(|e| SandboxError::Network(e.to_string()))?;

    let content = response.into_body().read_to_string()
        .map_err(|e| SandboxError::Network(e.to_string()))?;

    let profile = SandboxProfile {
        id: None,
        app_name: entry.app_name.clone(),
        profile_version: Some(entry.version.clone()),
        author: Some(entry.author.clone()),
        description: Some(entry.description.clone()),
        content,
        source: ProfileSource::Community {
            registry_id: entry.id.clone(),
        },
    };

    save_profile(db, &profile)
        .map_err(|e| SandboxError::Io(e.to_string()))?;

    Ok(profile)
}

/// Generate a default restrictive sandbox profile for an app.
pub fn generate_default_profile(app_name: &str) -> SandboxProfile {
    let content = format!(
        "# Default Driftwood sandbox profile for {}\n\
         # Generated automatically - review and customize before use\n\
         \n\
         include disable-common.inc\n\
         include disable-devel.inc\n\
         include disable-exec.inc\n\
         include disable-interpreters.inc\n\
         include disable-programs.inc\n\
         \n\
         whitelist ${{HOME}}/Documents\n\
         whitelist ${{HOME}}/Downloads\n\
         \n\
         caps.drop all\n\
         ipc-namespace\n\
         netfilter\n\
         no3d\n\
         nodvd\n\
         nogroups\n\
         noinput\n\
         nonewprivs\n\
         noroot\n\
         nosound\n\
         notv\n\
         nou2f\n\
         novideo\n\
         seccomp\n\
         tracelog\n",
        app_name,
    );

    SandboxProfile {
        id: None,
        app_name: app_name.to_string(),
        profile_version: Some("1.0".to_string()),
        author: Some("driftwood".to_string()),
        description: Some(format!("Default restrictive profile for {}", app_name)),
        content,
        source: ProfileSource::Local,
    }
}

/// Get the path to the profile file for an app (for passing to firejail --profile=).
pub fn profile_path_for_app(app_name: &str) -> Option<PathBuf> {
    let filename = sanitize_profile_name(app_name);
    let path = profiles_dir().join(format!("{}.profile", filename));
    if path.exists() { Some(path) } else { None }
}

// --- Community registry types ---

#[allow(dead_code)] // Used by community registry search
#[derive(Debug, Clone, serde::Deserialize)]
pub struct CommunityIndex {
    pub profiles: Vec<CommunityProfileEntry>,
}

#[allow(dead_code)] // Used by community registry search/download
#[derive(Debug, Clone, serde::Deserialize)]
pub struct CommunityProfileEntry {
    pub id: String,
    pub app_name: String,
    pub author: String,
    pub version: String,
    pub description: String,
    pub url: String,
    pub downloads: Option<u32>,
}

// --- Error types ---

#[derive(Debug)]
#[allow(dead_code)] // Network + Parse variants used by community registry functions
pub enum SandboxError {
    Io(String),
    Database(String),
    Network(String),
    Parse(String),
}

impl std::fmt::Display for SandboxError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Io(e) => write!(f, "I/O error: {}", e),
            Self::Database(e) => write!(f, "Database error: {}", e),
            Self::Network(e) => write!(f, "Network error: {}", e),
            Self::Parse(e) => write!(f, "Parse error: {}", e),
        }
    }
}

// --- Utility functions ---

fn sanitize_profile_name(name: &str) -> String {
    name.chars()
        .map(|c| if c.is_alphanumeric() || c == '-' || c == '_' { c.to_ascii_lowercase() } else { '-' })
        .collect::<String>()
        .trim_matches('-')
        .to_string()
}

fn format_profile_with_header(profile: &SandboxProfile) -> String {
    let mut header = String::new();
    header.push_str("# Driftwood Sandbox Profile\n");
    header.push_str(&format!("# App: {}\n", profile.app_name));
    if let Some(v) = &profile.profile_version {
        header.push_str(&format!("# Version: {}\n", v));
    }
    if let Some(a) = &profile.author {
        header.push_str(&format!("# Author: {}\n", a));
    }
    if let Some(d) = &profile.description {
        header.push_str(&format!("# Description: {}\n", d));
    }
    header.push_str(&format!("# Source: {}\n", profile.source.as_str()));
    header.push('\n');
    header.push_str(&profile.content);
    header
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_sanitize_profile_name() {
        assert_eq!(sanitize_profile_name("Firefox"), "firefox");
        assert_eq!(sanitize_profile_name("My Cool App"), "my-cool-app");
        assert_eq!(sanitize_profile_name("GIMP 2.10"), "gimp-2-10");
    }

    #[test]
    fn test_profile_source_as_str() {
        assert_eq!(ProfileSource::Local.as_str(), "local");
        assert_eq!(ProfileSource::FirejailDefault.as_str(), "firejail-default");
        assert_eq!(
            ProfileSource::Community { registry_id: "test".to_string() }.as_str(),
            "community"
        );
    }

    #[test]
    fn test_profile_source_from_record() {
        assert_eq!(
            ProfileSource::from_record("local", None),
            ProfileSource::Local
        );
        assert_eq!(
            ProfileSource::from_record("firejail-default", None),
            ProfileSource::FirejailDefault
        );
        match ProfileSource::from_record("community", Some("firefox-strict")) {
            ProfileSource::Community { registry_id } => assert_eq!(registry_id, "firefox-strict"),
            other => panic!("Expected Community, got {:?}", other),
        }
    }

    #[test]
    fn test_generate_default_profile() {
        let profile = generate_default_profile("Firefox");
        assert_eq!(profile.app_name, "Firefox");
        assert!(profile.content.contains("disable-common.inc"));
        assert!(profile.content.contains("seccomp"));
        assert!(profile.content.contains("nonewprivs"));
        assert!(profile.content.contains("Downloads"));
    }

    #[test]
    fn test_format_profile_with_header() {
        let profile = SandboxProfile {
            id: None,
            app_name: "TestApp".to_string(),
            profile_version: Some("1.0".to_string()),
            author: Some("tester".to_string()),
            description: Some("Test profile".to_string()),
            content: "include disable-common.inc\n".to_string(),
            source: ProfileSource::Local,
        };
        let output = format_profile_with_header(&profile);
        assert!(output.starts_with("# Driftwood Sandbox Profile\n"));
        assert!(output.contains("# App: TestApp"));
        assert!(output.contains("# Version: 1.0"));
        assert!(output.contains("# Author: tester"));
        assert!(output.contains("include disable-common.inc"));
    }

    #[test]
    fn test_profiles_dir_path() {
        let dir = profiles_dir();
        assert!(dir.to_string_lossy().contains("driftwood"));
        assert!(dir.to_string_lossy().contains("sandbox"));
    }

    #[test]
    fn test_sandbox_error_display() {
        let err = SandboxError::Io("permission denied".to_string());
        assert!(format!("{}", err).contains("permission denied"));
        let err = SandboxError::Database("db locked".to_string());
        assert!(format!("{}", err).contains("db locked"));
        let err = SandboxError::Network("connection refused".to_string());
        assert!(format!("{}", err).contains("connection refused"));
        let err = SandboxError::Parse("invalid json".to_string());
        assert!(format!("{}", err).contains("invalid json"));
    }

    #[test]
    fn test_save_and_load_profile() {
        let db = crate::core::database::Database::open_in_memory().unwrap();
        let profile = generate_default_profile("TestSaveApp");
        let result = save_profile(&db, &profile);
        assert!(result.is_ok());

        let loaded = load_profile(&db, "TestSaveApp").unwrap();
        assert!(loaded.is_some());
        let loaded = loaded.unwrap();
        assert_eq!(loaded.app_name, "TestSaveApp");
        assert!(loaded.content.contains("seccomp"));
    }

    #[test]
    fn test_list_profiles_empty() {
        let db = crate::core::database::Database::open_in_memory().unwrap();
        let profiles = list_profiles(&db);
        assert!(profiles.is_empty());
    }
}
