use crate::config::AppConfig;
use crate::error::{AppError, AppResult};
use crate::sidecar::supervise_magic::run_supervise_magic_raw;
use serde::{Deserialize, Serialize};
use std::process::Command;

const CFGUTIL_CANDIDATES: &[&str] = &[
    "/Applications/Apple Configurator.app/Contents/MacOS/cfgutil",
    "/Applications/Apple Configurator 2.app/Contents/MacOS/cfgutil",
];

fn first_executable(candidates: &[&str]) -> Option<String> {
    candidates
        .iter()
        .find(|path| std::path::Path::new(path).is_file())
        .map(|path| path.to_string())
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IphoneDeviceState {
    pub udid: String,
    pub name: String,
    pub product_type: String,
    pub product_version: String,
    pub is_supervised: bool,
    pub organization_name: Option<String>,
    pub find_my_enabled: Option<bool>,
    #[serde(rename = "installedProfileIDs")]
    pub installed_profile_ids: Vec<String>,
    #[serde(rename = "installedAppBundleIDs")]
    pub installed_app_bundle_ids: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SuperviseStatus {
    pub is_supervised: bool,
    pub organization_name: Option<String>,
    pub find_my_enabled: Option<bool>,
}

fn normalize_org_name(value: &str) -> String {
    value
        .trim()
        .trim_matches(|c| c == '"' || c == '\'')
        .to_string()
}

/// Parse `  Key:    Value` (check Format).
fn parse_colon(stdout: &str, key: &str) -> Option<String> {
    for raw in stdout.lines() {
        let trimmed = raw.trim();
        if let Some(rest) = trimmed.strip_prefix(&format!("{}:", key)) {
            return Some(rest.trim().to_string());
        }
    }
    None
}

/// Parse `  Key                 = Value` (cloud-config Format).
fn parse_equals(stdout: &str, key: &str) -> Option<String> {
    for raw in stdout.lines() {
        let trimmed = raw.trim();
        if let Some((k, v)) = trimmed.split_once('=') {
            if k.trim() == key {
                return Some(v.trim().to_string());
            }
        }
    }
    None
}

async fn run_supervise_magic_cmd(
    app: tauri::AppHandle,
    action: &str,
    args: &[&str],
) -> AppResult<String> {
    let result = run_supervise_magic_raw(app, action, args).await?;
    if !result.success {
        return Err(AppError::new(format!(
            "supervise-magic {} failed: {}",
            action,
            result.stderr
        )));
    }
    Ok(result.stdout)
}

fn run_cfgutil(binary: &str, args: &[&str]) -> AppResult<(String, String, bool)> {
    let output = Command::new(binary)
        .args(args)
        .output()
        .map_err(|e| AppError::new(format!("Failed to run cfgutil ({}): {}", binary, e)))?;

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
    Ok((stdout, stderr, output.status.success()))
}

pub async fn read_supervise_status(app: tauri::AppHandle) -> AppResult<SuperviseStatus> {
    let mut status = SuperviseStatus {
        is_supervised: false,
        organization_name: None,
        find_my_enabled: None,
    };

    // 1) cloud-config liest IsSupervised + OrganizationName direkt aus MCInstall.
    if let Ok(stdout) = run_supervise_magic_cmd(app.clone(), "cloud-config", &[]).await {
        if let Some(v) = parse_equals(&stdout, "IsSupervised") {
            status.is_supervised = v.to_lowercase() == "true";
        }
        if let Some(v) = parse_equals(&stdout, "OrganizationName") {
            status.organization_name = Some(normalize_org_name(&v));
        }
    }

    // 2) check gibt zusätzlich FindMyEnabled. Wenn check fehlschlägt, ist kein
    //    Gerät verbunden — dann liefern wir den Default-Status zurück.
    let check_stdout = match run_supervise_magic_cmd(app.clone(), "check", &[]).await {
        Ok(stdout) => stdout,
        Err(_) => return Ok(status),
    };

    if let Some(v) = parse_colon(&check_stdout, "FindMyEnabled") {
        status.find_my_enabled = Some(v.to_lowercase() == "true");
    }
    if !status.is_supervised {
        if let Some(v) = parse_colon(&check_stdout, "IsSupervised") {
            status.is_supervised = v.to_lowercase() == "true";
        }
    }
    if status.organization_name.is_none() {
        if let Some(v) = parse_colon(&check_stdout, "OrganizationName")
            .or_else(|| parse_colon(&check_stdout, "SupervisionOrganizationName"))
        {
            status.organization_name = Some(normalize_org_name(&v));
        }
    }

    Ok(status)
}

pub fn installed_profile_ids() -> AppResult<Vec<String>> {
    #[cfg(target_os = "macos")]
    {
        let cfgutil = first_executable(CFGUTIL_CANDIDATES)
            .ok_or_else(|| AppError::new("cfgutil nicht gefunden — bitte Apple Configurator installieren.".to_string()))?;
        let args = ["--foreach", "get", "configurationProfiles"];
        eprintln!("[ios_device] cfgutil profiles: binary={} args={:?}", cfgutil, args);
        let (stdout, stderr, success) = run_cfgutil(&cfgutil, &args)?;
        eprintln!(
            "[ios_device] cfgutil profiles: success={} stderr_len={} stdout_len={} stderr={:?}",
            success, stderr.len(), stdout.len(), stderr
        );
        eprintln!("[ios_device] cfgutil profiles stdout:\n{}", stdout);
        // cfgutil mit --foreach liefert auf manchen Setups Exit-Code 1, obwohl die Ausgabe gültig ist.
        // Wir behandeln es als Fehler nur, wenn stderr einen Fehler enthält.
        if !success && !stderr.trim().is_empty() {
            return Err(AppError::new(format!("cfgutil failed: {}", stderr)));
        }
        let list = parse_cfgutil_list(&stdout);
        eprintln!("[ios_device] cfgutil profiles parsed: {:?}", list);
        return Ok(list);
    }
    #[cfg(not(target_os = "macos"))]
    {
        // cfgutil is only available on macOS. On Windows we cannot enumerate profiles locally.
        Ok(Vec::new())
    }
}

pub fn installed_app_bundle_ids() -> AppResult<Vec<String>> {
    #[cfg(target_os = "macos")]
    {
        let cfgutil = first_executable(CFGUTIL_CANDIDATES)
            .ok_or_else(|| AppError::new("cfgutil nicht gefunden — bitte Apple Configurator installieren.".to_string()))?;
        let args = ["--foreach", "get", "installedApps"];
        eprintln!("[ios_device] cfgutil apps: binary={} args={:?}", cfgutil, args);
        let (stdout, stderr, success) = run_cfgutil(&cfgutil, &args)?;
        eprintln!(
            "[ios_device] cfgutil apps: success={} stderr_len={} stdout_len={} stderr={:?}",
            success, stderr.len(), stdout.len(), stderr
        );
        eprintln!("[ios_device] cfgutil apps stdout:\n{}", stdout);
        if !success && !stderr.trim().is_empty() {
            return Err(AppError::new(format!("cfgutil failed: {}", stderr)));
        }
        let list = parse_cfgutil_list(&stdout);
        eprintln!("[ios_device] cfgutil apps parsed: {:?}", list);
        return Ok(list);
    }
    #[cfg(not(target_os = "macos"))]
    {
        Ok(Vec::new())
    }
}

fn parse_cfgutil_list(stdout: &str) -> Vec<String> {
    stdout
        .lines()
        .filter_map(|line| {
            let trimmed = line.trim();
            if trimmed.is_empty() {
                return None;
            }
            // First token = identifier (split by tab or space)
            trimmed
                .split(|c: char| c == '\t' || c == ' ')
                .next()
                .map(|s| s.to_string())
        })
        .collect()
}

fn install_profile_via_cfgutil(path: &str) -> AppResult<()> {
    #[cfg(not(target_os = "macos"))]
    {
        return Err(AppError::new("Lokale Profil-Installation via cfgutil ist nur auf macOS verfügbar. Bitte das Profil per QR-Code installieren.".to_string()));
    }
    #[cfg(target_os = "macos")]
    let cfgutil = first_executable(CFGUTIL_CANDIDATES)
        .ok_or_else(|| AppError::new("cfgutil nicht gefunden — bitte Apple Configurator installieren.".to_string()))?;
    #[cfg(target_os = "macos")]
    let (_, stderr, success) = run_cfgutil(&cfgutil, &["--foreach", "install-profile", path])?;
    #[cfg(target_os = "macos")]
    if !success {
        let err = stderr.trim();
        if err.to_lowercase().contains("device is locked") {
            return Err(AppError::new("iPhone ist gesperrt. Bitte entsperren und erneut versuchen.".to_string()));
        }
        if err.to_lowercase().contains("user interaction")
            || err.to_lowercase().contains("benutzerinteraktion")
            || err.contains("MCInstallationErrorDomain Code: 4009")
        {
            return Err(AppError::new("iOS verlangt eine Bestätigung direkt am iPhone.".to_string()));
        }
        if err.contains("DMCInstallationErrorDomain") && err.contains("Code: 4020") {
            return Err(AppError::new("Lokale Profil-Installation ist durch iOS-Policy blockiert (DMC 4020).".to_string()));
        }
        return Err(AppError::new(format!("Profil-Installation fehlgeschlagen: {}", err)));
    }
    Ok(())
}

pub async fn detect_device_state(app: tauri::AppHandle) -> AppResult<Option<IphoneDeviceState>> {
    let check_stdout = match run_supervise_magic_cmd(app.clone(), "check", &[]).await {
        Ok(stdout) => stdout,
        Err(_) => return Ok(None), // Kein Gerät verbunden
    };

    let udid = match parse_colon(&check_stdout, "UDID") {
        Some(v) => v,
        None => return Ok(None),
    };
    let name = parse_colon(&check_stdout, "Name").unwrap_or_else(|| "iPhone".to_string());
    let product_type = parse_colon(&check_stdout, "Type").unwrap_or_default();
    let product_version = parse_colon(&check_stdout, "iOS")
        .or_else(|| parse_colon(&check_stdout, "ProductVersion"))
        .unwrap_or_default();

    let status = read_supervise_status(app.clone()).await?;

    let installed_profile_ids = tokio::task::spawn_blocking(installed_profile_ids)
        .await
        .map_err(|e| AppError::new(format!("profile detection task failed: {}", e)))?
        .unwrap_or_else(|e| {
            eprintln!("[ios_device] installed_profile_ids failed: {}", e);
            Vec::new()
        });
    let installed_app_bundle_ids = tokio::task::spawn_blocking(installed_app_bundle_ids)
        .await
        .map_err(|e| AppError::new(format!("app detection task failed: {}", e)))?
        .unwrap_or_else(|e| {
            eprintln!("[ios_device] installed_app_bundle_ids failed: {}", e);
            Vec::new()
        });

    eprintln!(
        "[ios_device] detect result: udid={} profiles={:?} apps={:?}",
        udid, installed_profile_ids, installed_app_bundle_ids
    );

    Ok(Some(IphoneDeviceState {
        udid,
        name,
        product_type,
        product_version,
        is_supervised: status.is_supervised,
        organization_name: status.organization_name,
        find_my_enabled: status.find_my_enabled,
        installed_profile_ids,
        installed_app_bundle_ids,
    }))
}

// MARK: - Tauri Commands

#[tauri::command]
pub async fn detect_iphone_state(app: tauri::AppHandle) -> AppResult<Option<IphoneDeviceState>> {
    detect_device_state(app).await
}

#[tauri::command]
pub async fn get_supervise_status(app: tauri::AppHandle) -> AppResult<SuperviseStatus> {
    read_supervise_status(app).await
}

#[tauri::command]
pub fn get_installed_profiles() -> AppResult<Vec<String>> {
    installed_profile_ids()
}

#[tauri::command]
pub fn get_installed_apps() -> AppResult<Vec<String>> {
    installed_app_bundle_ids()
}

#[tauri::command]
pub fn install_profile(path: String) -> AppResult<()> {
    install_profile_via_cfgutil(&path)
}

#[tauri::command]
pub async fn download_and_patch_enrollment_profile(url: String, udid: String) -> AppResult<String> {
    let response = reqwest::get(&url)
        .await
        .map_err(|e| AppError::new(format!("Failed to download enrollment profile: {}", e)))?;

    if !response.status().is_success() {
        return Err(AppError::new(format!(
            "MDM server returned status {}",
            response.status()
        )));
    }

    let mut text = response
        .text()
        .await
        .map_err(|e| AppError::new(format!("Failed to read enrollment profile: {}", e)))?;

    text = text.replace("%SerialNumber%", &udid);
    text = text.replace("%UDID%", &udid);

    let config_dir = AppConfig::config_dir()?;
    std::fs::create_dir_all(&config_dir)?;
    let profile_path = config_dir.join("rebreak-enrollment.mobileconfig");
    std::fs::write(&profile_path, text)?;

    Ok(profile_path.to_string_lossy().to_string())
}

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

    #[test]
    fn parse_equals_handles_multiple_spaces() {
        let input = "OrganizationName                         = ReBreak\nIsSupervised = true";
        assert_eq!(
            parse_equals(input, "OrganizationName"),
            Some("ReBreak".to_string())
        );
        assert_eq!(
            parse_equals(input, "IsSupervised"),
            Some("true".to_string())
        );
    }

    #[test]
    fn parse_cfgutil_list_extracts_bundle_ids() {
        let input = "com.example.app\tExample App v1\norg.rebreak.app\tReBreak v2\n\n";
        let list = parse_cfgutil_list(input);
        assert_eq!(list, vec!["com.example.app", "org.rebreak.app"]);
    }

    #[test]
    fn parse_cfgutil_list_handles_empty_profile_output() {
        // cfgutil --foreach get configurationProfiles kann leere stdout + Exit 1 liefern.
        assert!(parse_cfgutil_list("").is_empty());
        assert!(parse_cfgutil_list("   \n\n").is_empty());
    }

    #[test]
    fn iphone_state_serializes_id_casing() {
        let state = IphoneDeviceState {
            udid: "u".into(),
            name: "n".into(),
            product_type: "iPhone".into(),
            product_version: "26".into(),
            is_supervised: true,
            organization_name: None,
            find_my_enabled: None,
            installed_profile_ids: vec!["org.rebreak.mdm.enrollment".into()],
            installed_app_bundle_ids: vec!["org.rebreak.app".into()],
        };
        let json = serde_json::to_value(&state).unwrap();
        assert!(json.get("installedProfileIDs").is_some(), "expected installedProfileIDs");
        assert!(json.get("installedAppBundleIDs").is_some(), "expected installedAppBundleIDs");
        assert!(json.get("installedProfileIds").is_none(), "unexpected installedProfileIds");
    }

    #[test]
    #[cfg(target_os = "macos")]
    #[ignore = "requires connected iOS device"]
    fn cfgutil_live_outputs() {
        eprintln!("installed_profile_ids = {:?}", installed_profile_ids());
        eprintln!("installed_app_bundle_ids = {:?}", installed_app_bundle_ids());
    }
}
