use color_eyre::eyre::{eyre, Result};
use std::path::{Path, PathBuf};
use std::process::Stdio;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::sync::mpsc;

use super::types::{Architecture, Plugin, PluginType, SyncOptions, SyncStatus};

/// Check if yabridgectl is available in PATH
pub async fn is_yabridgectl_available() -> bool {
    tokio::process::Command::new("yabridgectl")
        .arg("--help")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .await
        .map(|s| s.success())
        .unwrap_or(false)
}

/// Run `yabridgectl status` and parse the output into Plugin structs
pub async fn get_status() -> Result<Vec<Plugin>> {
    let output = tokio::process::Command::new("yabridgectl")
        .arg("status")
        .output()
        .await
        .map_err(|e| eyre!("Failed to run yabridgectl status: {}", e))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(eyre!("yabridgectl status failed: {}", stderr));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    parse_status_output(&stdout)
}

/// Parse the raw output of `yabridgectl status`
///
/// Real format example:
/// ```text
/// yabridge path: <auto>
/// VST2 location: '/home/user/.vst/yabridge'
/// ...
/// libyabridge-chainloader-vst2.so: '...' (64-bit)
/// yabridge-host.exe: '...'
///
/// /home/user/.wine/drive_c/Program Files/Common Files/VST3/
///   FabFilter/FabFilter Pro-Q 3.vst3 :: VST3, legacy, 64-bit, synced
///   Serum_x64.dll :: VST2, 64-bit, not yet synced
/// ```
fn parse_status_output(output: &str) -> Result<Vec<Plugin>> {
    let mut plugins = Vec::new();
    let mut current_dir: Option<PathBuf> = None;

    for line in output.lines() {
        let trimmed = line.trim();

        // Skip empty lines
        if trimmed.is_empty() {
            continue;
        }

        // Skip info/header lines (key: value format or known prefixes)
        if trimmed.starts_with("yabridge path")
            || trimmed.starts_with("VST2 location")
            || trimmed.starts_with("VST3 location")
            || trimmed.starts_with("CLAP location")
            || trimmed.starts_with("libyabridge-")
            || trimmed.starts_with("yabridge-host")
        {
            continue;
        }

        // Detect directory headers: lines starting with '/' and ending with '/'
        // e.g. "/home/user/.wine/drive_c/Program Files/Common Files/VST3/"
        if trimmed.starts_with('/') && trimmed.ends_with('/') {
            current_dir = Some(PathBuf::from(trimmed));
            continue;
        }

        // Plugin lines contain " :: " as separator
        if trimmed.contains(" :: ") {
            if let Some(plugin) = parse_plugin_line(trimmed, current_dir.as_deref()) {
                plugins.push(plugin);
            }
        }
    }

    Ok(plugins)
}

/// Parse a single plugin line from status output
///
/// Real format: `  FabFilter/FabFilter Pro-Q 3.vst3 :: VST3, legacy, 64-bit, synced`
/// Or:          `  Serum_x64.dll :: VST2, 64-bit, synced`
fn parse_plugin_line(line: &str, parent_dir: Option<&Path>) -> Option<Plugin> {
    let trimmed = line.trim();

    // Split on " :: "
    let parts: Vec<&str> = trimmed.splitn(2, " :: ").collect();
    if parts.len() != 2 {
        return None;
    }

    let path_part = parts[0].trim();
    let info_part = parts[1].trim();

    // Parse the comma-separated info fields
    // Can be: "VST3, legacy, 64-bit, synced" or "VST2, 64-bit, synced"
    let fields: Vec<&str> = info_part.split(',').map(|s| s.trim()).collect();

    // First field is always the type
    let plugin_type = fields.first().and_then(|s| {
        let lower = s.to_lowercase();
        if lower.contains("vst2") {
            Some(PluginType::Vst2)
        } else if lower.contains("vst3") {
            Some(PluginType::Vst3)
        } else if lower.contains("clap") {
            Some(PluginType::Clap)
        } else {
            None
        }
    })?;

    // Find architecture field (contains "bit")
    let architecture = fields
        .iter()
        .find(|s| s.contains("bit"))
        .map(|s| {
            if s.contains("32") {
                Architecture::Win32
            } else if s.contains("64") {
                Architecture::Win64
            } else {
                Architecture::Unknown
            }
        })
        .unwrap_or(Architecture::Unknown);

    // Last field is the sync status
    let sync_status = fields
        .last()
        .map(|s| {
            let lower = s.to_lowercase();
            if lower.contains("not yet synced") || lower.contains("not synced") {
                SyncStatus::NotSynced
            } else if lower.contains("synced") {
                SyncStatus::Synced
            } else if lower.contains("symlink") {
                SyncStatus::Symlink
            } else if lower.contains("invalid") {
                SyncStatus::Invalid
            } else {
                SyncStatus::Unknown
            }
        })
        .unwrap_or(SyncStatus::Unknown);

    // Extract plugin name from the path part
    let plugin_path = PathBuf::from(path_part);
    let name = plugin_path
        .file_stem()
        .map(|s| s.to_string_lossy().to_string())
        .unwrap_or_else(|| path_part.to_string());

    let full_path = parent_dir
        .map(|d| d.join(path_part))
        .unwrap_or_else(|| plugin_path.clone());

    Some(Plugin {
        name,
        path: full_path,
        plugin_type,
        architecture,
        sync_status,
        parent_dir: parent_dir.unwrap_or(Path::new("")).to_path_buf(),
    })
}

/// Run `yabridgectl sync` with options, streaming output line by line
pub async fn sync_plugins(
    options: SyncOptions,
    output_tx: mpsc::UnboundedSender<String>,
) -> Result<()> {
    let mut cmd = tokio::process::Command::new("yabridgectl");
    cmd.arg("sync");

    if options.force {
        cmd.arg("--force");
    }
    if options.no_verify {
        cmd.arg("--no-verify");
    }
    if options.prune {
        cmd.arg("--prune");
    }
    if options.verbose {
        cmd.arg("--verbose");
    }

    cmd.stdout(Stdio::piped());
    cmd.stderr(Stdio::piped());

    let mut child = cmd
        .spawn()
        .map_err(|e| eyre!("Failed to spawn yabridgectl sync: {}", e))?;

    // Stream stdout
    if let Some(stdout) = child.stdout.take() {
        let reader = BufReader::new(stdout);
        let mut lines = reader.lines();
        let tx = output_tx.clone();
        tokio::spawn(async move {
            while let Ok(Some(line)) = lines.next_line().await {
                let _ = tx.send(line);
            }
        });
    }

    // Stream stderr
    if let Some(stderr) = child.stderr.take() {
        let reader = BufReader::new(stderr);
        let mut lines = reader.lines();
        let tx = output_tx.clone();
        tokio::spawn(async move {
            while let Ok(Some(line)) = lines.next_line().await {
                let _ = tx.send(format!("[stderr] {}", line));
            }
        });
    }

    let status = child
        .wait()
        .await
        .map_err(|e| eyre!("Failed to wait for yabridgectl sync: {}", e))?;

    if status.success() {
        let _ = output_tx.send("Sync completed successfully.".to_string());
        Ok(())
    } else {
        let msg = format!("Sync failed with exit code: {}", status);
        let _ = output_tx.send(msg.clone());
        Err(eyre!(msg))
    }
}

/// Add a plugin directory
pub async fn add_path(path: &Path) -> Result<String> {
    let output = tokio::process::Command::new("yabridgectl")
        .arg("add")
        .arg(path)
        .output()
        .await
        .map_err(|e| eyre!("Failed to run yabridgectl add: {}", e))?;

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();

    if output.status.success() {
        Ok(if stdout.is_empty() {
            format!("Added: {}", path.display())
        } else {
            stdout
        })
    } else {
        Err(eyre!(
            "Failed to add path: {}",
            if stderr.is_empty() { &stdout } else { &stderr }
        ))
    }
}

/// Remove a plugin directory
pub async fn remove_path(path: &Path) -> Result<String> {
    let output = tokio::process::Command::new("yabridgectl")
        .arg("rm")
        .arg(path)
        .output()
        .await
        .map_err(|e| eyre!("Failed to run yabridgectl rm: {}", e))?;

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();

    if output.status.success() {
        Ok(if stdout.is_empty() {
            format!("Removed: {}", path.display())
        } else {
            stdout
        })
    } else {
        Err(eyre!(
            "Failed to remove path: {}",
            if stderr.is_empty() { &stdout } else { &stderr }
        ))
    }
}

/// Run `yabridgectl list` and return the list of directories
pub async fn list_paths() -> Result<Vec<PathBuf>> {
    let output = tokio::process::Command::new("yabridgectl")
        .arg("list")
        .output()
        .await
        .map_err(|e| eyre!("Failed to run yabridgectl list: {}", e))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(eyre!("yabridgectl list failed: {}", stderr));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);

    Ok(stdout
        .lines()
        .filter(|l| !l.trim().is_empty())
        .map(|l| PathBuf::from(l.trim()))
        .collect())
}

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

    #[test]
    fn test_parse_plugin_line_vst3_with_legacy() {
        let line = "  FabFilter/FabFilter Pro-Q 3.vst3 :: VST3, legacy, 64-bit, synced";
        let plugin = parse_plugin_line(line, Some(Path::new("/home/user/.wine/VST3"))).unwrap();
        assert_eq!(plugin.name, "FabFilter Pro-Q 3");
        assert_eq!(plugin.plugin_type, PluginType::Vst3);
        assert_eq!(plugin.architecture, Architecture::Win64);
        assert_eq!(plugin.sync_status, SyncStatus::Synced);
        assert_eq!(
            plugin.path,
            PathBuf::from("/home/user/.wine/VST3/FabFilter/FabFilter Pro-Q 3.vst3")
        );
    }

    #[test]
    fn test_parse_plugin_line_vst2() {
        let line = "  Serum_x64.dll :: VST2, 64-bit, synced";
        let plugin = parse_plugin_line(line, Some(Path::new("/test"))).unwrap();
        assert_eq!(plugin.name, "Serum_x64");
        assert_eq!(plugin.plugin_type, PluginType::Vst2);
        assert_eq!(plugin.architecture, Architecture::Win64);
        assert_eq!(plugin.sync_status, SyncStatus::Synced);
    }

    #[test]
    fn test_parse_plugin_line_not_synced() {
        let line = "  Serum_x64.dll :: VST2, 64-bit, not yet synced";
        let plugin = parse_plugin_line(line, None).unwrap();
        assert_eq!(plugin.name, "Serum_x64");
        assert_eq!(plugin.sync_status, SyncStatus::NotSynced);
    }

    #[test]
    fn test_parse_plugin_line_vst3_bundle() {
        let line = "  SINE Player.vst3 :: VST3, bundle, 64-bit, synced";
        let plugin = parse_plugin_line(line, None).unwrap();
        assert_eq!(plugin.name, "SINE Player");
        assert_eq!(plugin.plugin_type, PluginType::Vst3);
        assert_eq!(plugin.sync_status, SyncStatus::Synced);
    }

    #[test]
    fn test_parse_plugin_line_nested_path() {
        let line = "  Neural DSP/Archetype Gojira.dll :: VST2, 64-bit, synced";
        let plugin = parse_plugin_line(line, Some(Path::new("/plugins"))).unwrap();
        assert_eq!(plugin.name, "Archetype Gojira");
        assert_eq!(plugin.plugin_type, PluginType::Vst2);
        assert_eq!(
            plugin.path,
            PathBuf::from("/plugins/Neural DSP/Archetype Gojira.dll")
        );
    }

    #[test]
    fn test_parse_status_output_real() {
        let output = r#"yabridge path: <auto>
VST2 location: '/home/user/.vst/yabridge'
VST3 location: '/home/user/.vst3/yabridge'
CLAP location: '/home/user/.clap/yabridge'

libyabridge-chainloader-vst2.so: '/usr/lib/libyabridge-chainloader-vst2.so' (64-bit)
libyabridge-chainloader-vst3.so: '/usr/lib/libyabridge-chainloader-vst3.so' (64-bit)

yabridge-host.exe: '/usr/bin/yabridge-host.exe'
yabridge-host-32.exe: <not found>

/home/user/.wine/drive_c/Program Files/Common Files/VST3/
  FabFilter/FabFilter Pro-Q 3.vst3 :: VST3, legacy, 64-bit, synced
  FabFilter/FabFilter Pro-R.vst3 :: VST3, legacy, 64-bit, synced

/home/user/.wine/drive_c/Program Files/Steinberg/VSTPlugins/
  Serum_x64.dll :: VST2, 64-bit, synced
  Neural DSP/Archetype Gojira.dll :: VST2, 64-bit, not yet synced
"#;
        let plugins = parse_status_output(output).unwrap();
        assert_eq!(plugins.len(), 4);

        assert_eq!(plugins[0].name, "FabFilter Pro-Q 3");
        assert_eq!(plugins[0].plugin_type, PluginType::Vst3);
        assert_eq!(plugins[0].sync_status, SyncStatus::Synced);

        assert_eq!(plugins[2].name, "Serum_x64");
        assert_eq!(plugins[2].plugin_type, PluginType::Vst2);

        assert_eq!(plugins[3].name, "Archetype Gojira");
        assert_eq!(plugins[3].sync_status, SyncStatus::NotSynced);
    }

    #[test]
    fn test_parse_skips_info_lines() {
        let output = "yabridge path: <auto>\nlibyabridge-chainloader-vst2.so: '/usr/lib/foo' (64-bit)\nyabridge-host.exe: '/usr/bin/yabridge-host.exe'\n";
        let plugins = parse_status_output(output).unwrap();
        assert_eq!(plugins.len(), 0);
    }
}
