use super::*;
use std::collections::BTreeMap;
use tempfile::TempDir;

fn make_lock_package(name: &str, bins: &[&str]) -> LockPackage {
    LockPackage {
        name: name.to_string(),
        description: None,
        version: "1.0.0".to_string(),
        bin: bins.iter().map(|s| (*s).to_string()).collect(),
        require: BTreeMap::new(),
        require_dev: BTreeMap::new(),
        replace: BTreeMap::new(),
        provide: BTreeMap::new(),
        conflict: BTreeMap::new(),
        autoload: Default::default(),
        autoload_dev: serde_json::Value::Null,
        extra: serde_json::Value::Null,
        version_normalized: None,
        dist: None,
        source: None,
        transport_options: serde_json::Value::Null,
        package_type: None,
        time: None,
        license: Vec::new(),
        funding: Vec::new(),
    }
}

fn setup_package_bin(root: &Path, pkg_name: &str, bin_path: &str, content: &[u8]) {
    let full = root.join("vendor").join(pkg_name).join(bin_path);
    std::fs::create_dir_all(full.parent().unwrap()).unwrap();
    std::fs::write(&full, content).unwrap();
}

#[test]
fn php_bin_generates_php_proxy() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"<?php\necho 'hello';\n");

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    assert!(summary.warnings.is_empty(), "{:?}", summary.warnings);

    let proxy = root.join("vendor/bin/run");
    assert!(proxy.exists());
    let content = std::fs::read_to_string(&proxy).unwrap();
    assert!(content.starts_with("#!/usr/bin/env php\n<?php"));
    assert!(content.contains("Proxy PHP file generated by Composer"));
    assert!(content.contains("$GLOBALS['_composer_bin_dir']"));
    assert!(content.contains("$GLOBALS['_composer_autoload_path']"));
    assert!(content.contains("'/..'.'/acme/tool/bin/run'"));
}

#[test]
fn shell_bin_generates_shell_proxy() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"#!/usr/bin/env bash\necho hi\n");

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    let content = std::fs::read_to_string(root.join("vendor/bin/run")).unwrap();
    assert!(content.starts_with("#!/usr/bin/env sh\n"));
    assert!(content.contains("COMPOSER_RUNTIME_BIN_DIR"));
    assert!(content.contains("exec \"${dir}/run\""));
}

#[test]
fn php_shebang_detected_as_php() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(
        root,
        "acme/tool",
        "bin/run",
        b"#!/usr/bin/env php\n<?php\necho 'hello';\n",
    );

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    let content = std::fs::read_to_string(root.join("vendor/bin/run")).unwrap();
    assert!(content.contains("Proxy PHP file generated by Composer"));
}

#[test]
fn missing_target_warns() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    std::fs::create_dir_all(root.join("vendor/acme/tool")).unwrap();

    let pkg = make_lock_package("acme/tool", &["bin/missing"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 0);
    assert_eq!(summary.warnings.len(), 1);
    assert!(summary.warnings[0].contains("file not found"));
}

#[test]
fn directory_target_warns() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    std::fs::create_dir_all(root.join("vendor/acme/tool/bin/run")).unwrap();

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 0);
    assert!(summary.warnings[0].contains("directory"));
}

#[test]
fn existing_user_file_not_overwritten() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"<?php\necho 1;\n");
    let bin_dir = root.join("vendor/bin");
    std::fs::create_dir_all(&bin_dir).unwrap();
    std::fs::write(bin_dir.join("run"), "user content").unwrap();

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 0);
    assert!(summary.warnings[0].contains("conflicts"));
    let content = std::fs::read_to_string(bin_dir.join("run")).unwrap();
    assert_eq!(content, "user content");
}

#[test]
fn existing_proxy_is_overwritten() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"<?php\necho 1;\n");
    let bin_dir = root.join("vendor/bin");
    std::fs::create_dir_all(&bin_dir).unwrap();
    std::fs::write(
        bin_dir.join("run"),
        "#!/usr/bin/env php\n<?php\n// Proxy PHP file generated by Composer\nold",
    )
    .unwrap();

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    let content = std::fs::read_to_string(bin_dir.join("run")).unwrap();
    assert!(content.contains("acme/tool/bin/run"));
}

#[cfg(unix)]
#[test]
fn proxy_is_executable() {
    use std::os::unix::fs::PermissionsExt;

    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"<?php\necho 1;\n");

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    install_bin_proxies(root, &pkgs);

    let proxy_mode = root
        .join("vendor/bin/run")
        .metadata()
        .unwrap()
        .permissions()
        .mode();
    assert_ne!(proxy_mode & 0o111, 0, "proxy must be executable");

    let target_mode = root
        .join("vendor/acme/tool/bin/run")
        .metadata()
        .unwrap()
        .permissions()
        .mode();
    assert_ne!(target_mode & 0o111, 0, "target must be executable");
}

#[test]
fn no_bin_field_is_noop() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();

    let pkg = make_lock_package("acme/tool", &[]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 0);
    assert!(summary.warnings.is_empty());
    assert!(!root.join("vendor/bin").exists());
}

#[test]
fn basename_used_for_link_name() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "src/bin/my-tool", b"<?php\necho 1;\n");

    let pkg = make_lock_package("acme/tool", &["src/bin/my-tool"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    assert!(root.join("vendor/bin/my-tool").exists());
    assert!(!root.join("vendor/bin/src").exists());
}

#[test]
fn remove_cleans_up_proxies() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"<?php\necho 1;\n");

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    install_bin_proxies(root, &pkgs);
    assert!(root.join("vendor/bin/run").exists());

    remove_bin_proxies(root, &pkgs);
    assert!(!root.join("vendor/bin/run").exists());
    assert!(!root.join("vendor/bin").exists(), "empty bin dir should be removed");
}

#[test]
fn php_shebang_generates_stream_wrapper() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(
        root,
        "acme/tool",
        "bin/run",
        b"#!/usr/bin/env php\n<?php\necho 'hello';\n",
    );

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    let content = std::fs::read_to_string(root.join("vendor/bin/run")).unwrap();
    assert!(content.contains("BinProxyWrapper"), "should include stream wrapper class");
    assert!(content.contains("PHP_VERSION_ID < 80000"));
    assert!(content.contains("phpvfscomposer://"));
    assert!(content.contains("using a stream wrapper to prevent the shebang"));
}

#[test]
fn phpunit_gets_isolation_globals() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(
        root,
        "phpunit/phpunit",
        "phpunit",
        b"#!/usr/bin/env php\n<?php\n// phpunit\n",
    );

    let pkg = make_lock_package("phpunit/phpunit", &["phpunit"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 1);
    let content = std::fs::read_to_string(root.join("vendor/bin/phpunit")).unwrap();
    assert!(content.contains("__PHPUNIT_ISOLATION_EXCLUDE_LIST"));
    assert!(content.contains("__PHPUNIT_ISOLATION_BLACKLIST"));
}

#[test]
fn plain_php_no_stream_wrapper() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/tool", "bin/run", b"<?php\necho 'hello';\n");

    let pkg = make_lock_package("acme/tool", &["bin/run"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg];
    install_bin_proxies(root, &pkgs);

    let content = std::fs::read_to_string(root.join("vendor/bin/run")).unwrap();
    assert!(!content.contains("BinProxyWrapper"), "plain PHP should not have stream wrapper");
    assert!(!content.contains("phpvfscomposer"));
}

#[test]
fn multiple_packages_multiple_bins() {
    let tmp = TempDir::new().unwrap();
    let root = tmp.path();
    setup_package_bin(root, "acme/foo", "bin/foo", b"<?php\necho 1;\n");
    setup_package_bin(root, "acme/bar", "bin/bar", b"#!/bin/sh\necho hi\n");
    setup_package_bin(root, "acme/bar", "bin/baz", b"<?php\necho 2;\n");

    let pkg1 = make_lock_package("acme/foo", &["bin/foo"]);
    let pkg2 = make_lock_package("acme/bar", &["bin/bar", "bin/baz"]);
    let pkgs: Vec<&LockPackage> = vec![&pkg1, &pkg2];
    let summary = install_bin_proxies(root, &pkgs);

    assert_eq!(summary.bins_installed, 3);
    assert!(root.join("vendor/bin/foo").exists());
    assert!(root.join("vendor/bin/bar").exists());
    assert!(root.join("vendor/bin/baz").exists());

    let foo_content = std::fs::read_to_string(root.join("vendor/bin/foo")).unwrap();
    assert!(foo_content.contains("Proxy PHP file generated by Composer"));

    let bar_content = std::fs::read_to_string(root.join("vendor/bin/bar")).unwrap();
    assert!(bar_content.contains("COMPOSER_RUNTIME_BIN_DIR"));
}
