use proxywarden_lib::component_detection::{
    detect_proxyfier_install_with_host, detect_singbox_install_with_host,
    proxyfier_component_from_detection, singbox_component_from_detection, ProxyfierDetectionHost,
    ProxyfierEngine, RegistryInstallEntry,
};
use proxywarden_lib::models::ComponentState;
use std::{
    collections::{HashMap, HashSet},
    path::{Path, PathBuf},
};

#[test]
fn detects_existing_proxifyre_from_registry_install_location() {
    let host = MockHost::new()
        .with_registry("ProxiFyre", r"C:\Tools\ProxiFyre")
        .with_path(r"C:\Tools\ProxiFyre")
        .with_service("ProxiFyreService");

    let detected = detect_proxyfier_install_with_host(&host)
        .expect("existing ProxiFyre install should be detected");

    assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre);
    assert_eq!(detected.install_dir, PathBuf::from(r"C:\Tools\ProxiFyre"));
    assert_eq!(
        detected.config_path,
        Some(PathBuf::from(r"C:\Tools\ProxiFyre\app-config.json"))
    );
    assert!(detected.running);

    let component = proxyfier_component_from_detection(Some(&detected));
    assert_eq!(component.state, ComponentState::Running);
    assert!(component.installed);
    assert!(component.running);
    assert_eq!(component.path, Some(r"C:\Tools\ProxiFyre".to_string()));
    assert!(component.problems.is_empty());
}

#[test]
fn ignores_plain_proxifier_install() {
    let host = MockHost::new()
        .with_registry("Proxifier", r"C:\Program Files\Proxifier")
        .with_path(r"C:\Program Files\Proxifier")
        .with_process("Proxifier.exe");

    assert!(detect_proxyfier_install_with_host(&host).is_none());
}

#[test]
fn env_override_can_point_to_portable_proxifyre_install() {
    let host = MockHost::new()
        .with_env("PROXYWARDEN_PROXIFYRE_ROOT", r"D:\Portable\ProxiFyre")
        .with_path(r"D:\Portable\ProxiFyre\ProxiFyre.exe");

    let detected = detect_proxyfier_install_with_host(&host)
        .expect("env override should be checked before common paths");

    assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre);
    assert_eq!(
        detected.executable_path,
        PathBuf::from(r"D:\Portable\ProxiFyre\ProxiFyre.exe")
    );
}

#[test]
fn missing_proxyfier_returns_install_action_status() {
    let component = proxyfier_component_from_detection(None);

    assert_eq!(component.state, ComponentState::Missing);
    assert!(!component.installed);
    assert_eq!(component.actions, vec!["Установить ProxiFyre"]);
}

#[test]
fn detects_running_local_singbox_from_default_install_root_and_service() {
    let host = MockHost::new()
        .with_path(r"C:\Program Files\ProxyWarden\sing-box\sing-box.exe")
        .with_service("ProxyWardenSingBox");

    let detected =
        detect_singbox_install_with_host(&host).expect("existing sing-box should be detected");

    assert_eq!(
        detected.executable_path,
        PathBuf::from(r"C:\Program Files\ProxyWarden\sing-box\sing-box.exe")
    );
    assert_eq!(detected.service_name, "ProxyWardenSingBox");
    assert!(detected.running);

    let component = singbox_component_from_detection(Some(&detected));
    assert_eq!(component.state, ComponentState::Running);
    assert!(component.installed);
    assert!(component.running);
    assert_eq!(
        component.path,
        Some(r"C:\Program Files\ProxyWarden\sing-box\sing-box.exe".to_string())
    );
    assert!(component.problems.is_empty());
}

#[test]
fn detects_stopped_local_singbox_from_env_override() {
    let host = MockHost::new()
        .with_env("PROXYWARDEN_SINGBOX_ROOT", r"D:\Portable\sing-box")
        .with_path(r"D:\Portable\sing-box\sing-box.exe");

    let detected = detect_singbox_install_with_host(&host).expect("env override should be checked");
    let component = singbox_component_from_detection(Some(&detected));

    assert_eq!(
        detected.executable_path,
        PathBuf::from(r"D:\Portable\sing-box\sing-box.exe")
    );
    assert_eq!(component.state, ComponentState::Stopped);
    assert!(component.installed);
    assert!(!component.running);
    assert!(component
        .problems
        .iter()
        .any(|problem| problem.contains("остановлена")));
}

#[test]
fn missing_local_singbox_returns_optional_install_action_status() {
    let component = singbox_component_from_detection(None);

    assert_eq!(component.state, ComponentState::Missing);
    assert!(!component.installed);
    assert!(!component.running);
    assert_eq!(component.actions, vec!["Установить Local sing-box"]);
    assert!(component.problems.is_empty());
}

#[derive(Default)]
struct MockHost {
    env: HashMap<String, String>,
    paths: HashSet<String>,
    processes: HashSet<String>,
    services: HashSet<String>,
    registry: Vec<RegistryInstallEntry>,
}

impl MockHost {
    fn new() -> Self {
        Self::default()
    }

    fn with_env(mut self, name: &str, value: &str) -> Self {
        self.env.insert(name.to_string(), value.to_string());
        self
    }

    fn with_path(mut self, path: &str) -> Self {
        self.paths.insert(normalize_path(path));
        self
    }

    fn with_process(mut self, process: &str) -> Self {
        self.processes.insert(process.to_ascii_lowercase());
        self
    }

    fn with_service(mut self, service: &str) -> Self {
        self.services.insert(service.to_ascii_lowercase());
        self
    }

    fn with_registry(mut self, display_name: &str, install_location: &str) -> Self {
        self.registry.push(RegistryInstallEntry {
            display_name: display_name.to_string(),
            install_location: Some(PathBuf::from(install_location)),
            display_icon: None,
        });
        self
    }
}

impl ProxyfierDetectionHost for MockHost {
    fn env_var(&self, name: &str) -> Option<String> {
        self.env.get(name).cloned()
    }

    fn path_exists(&self, path: &Path) -> bool {
        self.paths
            .contains(&normalize_path(&path.display().to_string()))
    }

    fn process_running(&self, process_name: &str) -> bool {
        self.processes.contains(&process_name.to_ascii_lowercase())
    }

    fn service_running(&self, service_name: &str) -> bool {
        self.services.contains(&service_name.to_ascii_lowercase())
    }

    fn registry_install_entries(&self) -> Vec<RegistryInstallEntry> {
        self.registry.clone()
    }
}

fn normalize_path(path: &str) -> String {
    path.replace('/', "\\").to_ascii_lowercase()
}
