use crate::bottle::homebrew_prefix;
use crate::error::{validate_package_name, Result, OilError};
use crate::install::InstallState;
use console::style;
#[cfg(target_os = "macos")]
use std::path::Path;
use std::path::PathBuf;
use tokio::process::Command;
use tracing::instrument;

#[derive(Debug, Clone)]
struct ServiceInfo {
    name: String,
    status: ServiceStatus,
    #[allow(dead_code)]
    plist_path: Option<PathBuf>,
    pid: Option<u32>,
}

#[derive(Debug, Clone, PartialEq)]
enum ServiceStatus {
    Running,
    Stopped,
}

impl std::fmt::Display for ServiceStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ServiceStatus::Running => write!(f, "running"),
            ServiceStatus::Stopped => write!(f, "stopped"),
        }
    }
}

fn services_dir() -> PathBuf {
    let prefix = homebrew_prefix();
    prefix.join("opt")
}

#[cfg(target_os = "macos")]
fn launchctl_plist_dir() -> PathBuf {
    if let Ok(home) = std::env::var("HOME") {
        PathBuf::from(home).join("Library/LaunchAgents")
    } else {
        PathBuf::from("/Library/LaunchDaemons")
    }
}

fn find_service_plist(formula_name: &str) -> Option<PathBuf> {
    let opt_dir = services_dir().join(formula_name);

    let search_dirs = [
        opt_dir.join("homebrew.mxcl.".to_string() + formula_name + ".plist"),
        opt_dir.join(formula_name.to_string() + ".plist"),
    ];

    for path in &search_dirs {
        if path.exists() {
            return Some(path.clone());
        }
    }

    let plist_pattern = format!("homebrew.mxcl.{}.plist", formula_name);
    let alt_pattern = format!("{}.plist", formula_name);

    if let Ok(entries) = std::fs::read_dir(&opt_dir) {
        for entry in entries.flatten() {
            let name = entry.file_name().to_string_lossy().to_string();
            if name == plist_pattern || name == alt_pattern || name.ends_with(".plist") {
                return Some(entry.path());
            }
        }
    }

    let cellar_opt = homebrew_prefix().join("Cellar").join(formula_name);
    if let Ok(versions) = std::fs::read_dir(&cellar_opt) {
        for version_entry in versions.flatten() {
            let version_dir = version_entry.path();
            let plist_in_cellar = version_dir.join(&plist_pattern);
            if plist_in_cellar.exists() {
                return Some(plist_in_cellar);
            }
            let alt_in_cellar = version_dir.join(&alt_pattern);
            if alt_in_cellar.exists() {
                return Some(alt_in_cellar);
            }
            if let Ok(files) = std::fs::read_dir(&version_dir) {
                for file in files.flatten() {
                    let fname = file.file_name().to_string_lossy().to_string();
                    if fname.ends_with(".plist") {
                        return Some(file.path());
                    }
                }
            }
        }
    }

    None
}

#[cfg(target_os = "linux")]
fn find_systemd_unit(formula_name: &str) -> Option<PathBuf> {
    let opt_dir = services_dir().join(formula_name);
    let unit_name = format!("homebrew.{}.service", formula_name);

    let path = opt_dir.join(&unit_name);
    if path.exists() {
        return Some(path);
    }

    let cellar_opt = homebrew_prefix().join("Cellar").join(formula_name);
    if let Ok(versions) = std::fs::read_dir(&cellar_opt) {
        for version_entry in versions.flatten() {
            let unit_in_cellar = version_entry.path().join(&unit_name);
            if unit_in_cellar.exists() {
                return Some(unit_in_cellar);
            }
        }
    }

    None
}

#[instrument]
pub async fn services_list() -> Result<()> {
    let state = InstallState::new()?;
    state.sync_from_cellar().await.ok();
    let installed = state.load().await?;

    let mut services = Vec::new();

    for name in installed.keys() {
        let plist = find_service_plist(name);
        if plist.is_some() {
            let status = get_service_status(name).await;
            let pid = get_service_pid(name).await;
            services.push(ServiceInfo {
                name: name.clone(),
                status,
                plist_path: plist,
                pid,
            });
        }

        #[cfg(target_os = "linux")]
        {
            if find_systemd_unit(name).is_some() {
                let status = get_service_status(name).await;
                services.push(ServiceInfo {
                    name: name.clone(),
                    status,
                    plist_path: None,
                    pid: None,
                });
            }
        }
    }

    if services.is_empty() {
        println!("no services found");
        return Ok(());
    }

    services.sort_by(|a, b| a.name.cmp(&b.name));

    println!();
    for svc in &services {
        let status_style = match svc.status {
            ServiceStatus::Running => style("running").green(),
            ServiceStatus::Stopped => style("stopped").dim(),
        };

        let pid_str = svc.pid.map(|p| format!(" (pid {})", p)).unwrap_or_default();

        println!(
            "  {} {} {}",
            style(&svc.name).magenta(),
            status_style,
            style(&pid_str).dim()
        );
    }

    let running = services
        .iter()
        .filter(|s| s.status == ServiceStatus::Running)
        .count();
    let stopped = services.len() - running;
    println!(
        "\n{} services ({} running, {} stopped)",
        style(services.len()).cyan(),
        style(running).green(),
        style(stopped).dim()
    );

    Ok(())
}

#[instrument]
pub async fn services_start(formula_name: &str, nice: Option<i32>) -> Result<()> {
    validate_package_name(formula_name)?;
    let state = InstallState::new()?;
    let installed = state.load().await?;

    if !installed.contains_key(formula_name) {
        return Err(OilError::NotInstalled(formula_name.to_string()));
    }

    #[cfg(target_os = "macos")]
    {
        let plist = find_service_plist(formula_name).ok_or_else(|| {
            OilError::ServiceError(format!(
                "{} does not have a service definition (no plist found)",
                formula_name
            ))
        })?;

        let target_dir = launchctl_plist_dir();
        std::fs::create_dir_all(&target_dir)?;

        let plist_name = plist.file_name().unwrap().to_string_lossy().to_string();
        let target_plist = target_dir.join(&plist_name);

        let mut plist_content = std::fs::read_to_string(&plist)?;

        if let Some(priority) = nice {
            if !(-20..=20).contains(&priority) {
                return Err(OilError::ServiceError(
                    "Nice priority must be between -20 and 20".to_string(),
                ));
            }
            if !plist_content.contains("<key>Nice</key>") {
                let nice_entry = format!(
                    "\t<key>Nice</key>\n\t<integer>{}</integer>\n</dict>",
                    priority
                );
                plist_content = plist_content.replace("</dict>", &nice_entry);
            }
        }

        if !plist_content.contains("<key>ThrottleInterval</key>") {
            let throttle = "\t<key>ThrottleInterval</key>\n\t<integer>60</integer>\n</dict>";
            plist_content = plist_content.replace("</dict>", throttle);
        }

        std::fs::write(&target_plist, &plist_content)?;

        let _label = plist_name.trim_end_matches(".plist");
        let output = Command::new("launchctl")
            .args(["load", "-w"])
            .arg(&target_plist)
            .output()
            .await
            .map_err(|e| OilError::ServiceError(format!("launchctl failed: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            if !stderr.contains("already loaded") {
                return Err(OilError::ServiceError(format!(
                    "launchctl load failed: {}",
                    stderr
                )));
            }
        }

        println!(
            "{} {} started",
            style("✓").green(),
            style(formula_name).magenta()
        );
    }

    #[cfg(target_os = "linux")]
    {
        let unit = find_systemd_unit(formula_name).ok_or_else(|| {
            OilError::ServiceError(format!(
                "{} does not have a systemd service unit",
                formula_name
            ))
        })?;

        let systemd_user_dir =
            PathBuf::from(std::env::var("HOME").unwrap_or_default()).join(".config/systemd/user");
        std::fs::create_dir_all(&systemd_user_dir)?;

        let unit_name = unit.file_name().unwrap().to_string_lossy().to_string();
        let target_unit = systemd_user_dir.join(&unit_name);
        std::fs::copy(&unit, &target_unit)?;

        let output = Command::new("systemctl")
            .args(["--user", "enable", "--now", &unit_name])
            .output()
            .await
            .map_err(|e| OilError::ServiceError(format!("systemctl failed: {}", e)))?;

        if !output.status.success() {
            return Err(OilError::ServiceError(format!(
                "systemctl enable failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        println!(
            "{} {} started",
            style("✓").green(),
            style(formula_name).magenta()
        );
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    {
        return Err(OilError::ServiceError(
            "Service management not supported on this platform".to_string(),
        ));
    }

    Ok(())
}

#[instrument]
pub async fn services_stop(formula_name: &str) -> Result<()> {
    validate_package_name(formula_name)?;
    #[cfg(target_os = "macos")]
    {
        let plist_dir = launchctl_plist_dir();
        let plist_name = format!("homebrew.mxcl.{}.plist", formula_name);
        let plist_path = plist_dir.join(&plist_name);

        if !plist_path.exists() {
            let alt_name = format!("{}.plist", formula_name);
            let alt_path = plist_dir.join(&alt_name);
            if alt_path.exists() {
                return stop_launchctl(&alt_path, formula_name).await;
            }
            return Err(OilError::ServiceError(format!(
                "{} service is not running",
                formula_name
            )));
        }

        return stop_launchctl(&plist_path, formula_name).await;
    }

    #[cfg(target_os = "linux")]
    {
        let unit_name = format!("homebrew.{}.service", formula_name);
        let output = Command::new("systemctl")
            .args(["--user", "disable", "--now", &unit_name])
            .output()
            .await
            .map_err(|e| OilError::ServiceError(format!("systemctl failed: {}", e)))?;

        if !output.status.success() {
            return Err(OilError::ServiceError(format!(
                "systemctl disable failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        println!(
            "{} {} stopped",
            style("✓").green(),
            style(formula_name).magenta()
        );
        return Ok(());
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    Err(OilError::ServiceError(
        "Service management not supported on this platform".to_string(),
    ))
}

#[cfg(target_os = "macos")]
async fn stop_launchctl(plist_path: &Path, formula_name: &str) -> Result<()> {
    let output = Command::new("launchctl")
        .args(["unload", "-w"])
        .arg(plist_path)
        .output()
        .await
        .map_err(|e| OilError::ServiceError(format!("launchctl failed: {}", e)))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        if !stderr.contains("Could not find specified service") {
            return Err(OilError::ServiceError(format!(
                "launchctl unload failed: {}",
                stderr
            )));
        }
    }

    let _ = std::fs::remove_file(plist_path);

    println!(
        "{} {} stopped",
        style("✓").green(),
        style(formula_name).magenta()
    );
    Ok(())
}

#[instrument]
pub async fn services_restart(formula_name: &str, nice: Option<i32>) -> Result<()> {
    let _ = services_stop(formula_name).await;
    services_start(formula_name, nice).await
}

async fn get_service_status(formula_name: &str) -> ServiceStatus {
    #[cfg(target_os = "macos")]
    {
        let labels = [
            format!("homebrew.mxcl.{}", formula_name),
            formula_name.to_string(),
        ];

        for label in &labels {
            let output = Command::new("launchctl")
                .args(["list", label])
                .output()
                .await;

            if let Ok(out) = output {
                if out.status.success() {
                    return ServiceStatus::Running;
                }
            }
        }

        let plist_dir = launchctl_plist_dir();
        let plist = plist_dir.join(format!("homebrew.mxcl.{}.plist", formula_name));
        if plist.exists() {
            return ServiceStatus::Stopped;
        }

        ServiceStatus::Stopped
    }

    #[cfg(target_os = "linux")]
    {
        let unit = format!("homebrew.{}.service", formula_name);
        let output = Command::new("systemctl")
            .args(["--user", "is-active", &unit])
            .output()
            .await;

        match output {
            Ok(out) => {
                let stdout = String::from_utf8_lossy(&out.stdout);
                if stdout.trim() == "active" {
                    ServiceStatus::Running
                } else {
                    ServiceStatus::Stopped
                }
            }
            Err(_) => ServiceStatus::Stopped,
        }
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    ServiceStatus::Stopped
}

async fn get_service_pid(formula_name: &str) -> Option<u32> {
    #[cfg(target_os = "macos")]
    {
        let label = format!("homebrew.mxcl.{}", formula_name);
        let output = Command::new("launchctl")
            .args(["list", &label])
            .output()
            .await
            .ok()?;

        if !output.status.success() {
            return None;
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        for line in stdout.lines() {
            let line = line.trim();
            if line.starts_with("\"PID\"") || line.contains("PID") {
                let parts: Vec<&str> = line.split(|c: char| !c.is_ascii_digit()).collect();
                for part in parts {
                    if let Ok(pid) = part.parse::<u32>() {
                        if pid > 0 {
                            return Some(pid);
                        }
                    }
                }
            }
        }

        None
    }

    #[cfg(target_os = "linux")]
    {
        let unit = format!("homebrew.{}.service", formula_name);
        let output = Command::new("systemctl")
            .args(["--user", "show", "--property=MainPID", &unit])
            .output()
            .await
            .ok()?;

        if !output.status.success() {
            return None;
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let pid_str = stdout.trim().strip_prefix("MainPID=")?;
        let pid: u32 = pid_str.parse().ok()?;
        if pid > 0 {
            Some(pid)
        } else {
            None
        }
    }

    #[cfg(not(any(target_os = "macos", target_os = "linux")))]
    None
}
