use std::{fs, path::Path};

const CRATE_DIR: &str = env!("CARGO_MANIFEST_DIR");

#[test]
fn desktop_packages_have_native_icons_and_cargo_commands() {
    let assets = Path::new(CRATE_DIR).join("assets/app-icons");
    let png = fs::read(assets.join("bds.png")).expect("Linux PNG icon");
    let ico = fs::read(assets.join("bds.ico")).expect("Windows ICO icon");
    let icns = fs::read(assets.join("bds.icns")).expect("macOS ICNS icon");

    assert_eq!(&png[..8], b"\x89PNG\r\n\x1a\n");
    assert_eq!(u32::from_be_bytes(png[16..20].try_into().unwrap()), 1024);
    assert_eq!(u32::from_be_bytes(png[20..24].try_into().unwrap()), 1024);
    assert_eq!(&ico[..4], &[0, 0, 1, 0]);
    assert!(u16::from_le_bytes(ico[4..6].try_into().unwrap()) >= 6);
    assert_eq!(&icns[..4], b"icns");

    let manifest = fs::read_to_string(Path::new(CRATE_DIR).join("Cargo.toml")).unwrap();
    for required in [
        "[package.metadata.packager]",
        "assets/app-icons/bds.png",
        "assets/app-icons/bds.ico",
        "assets/app-icons/bds.icns",
        "identifier = \"de.rfc1437.ruds\"",
        "deep-link-protocols = [{ schemes = [\"ruds\"] }]",
        "minimum-system-version = \"26.0\"",
        "[package.metadata.packager.deb]",
        "libgtk-3-0t64 | libgtk-3-0",
        "libwebkit2gtk-4.1-0",
        "libxdo3",
        "../../target/release/bds-runtime/*",
        "cargo run --quiet -p bds-package-runtime",
        "cargo run --quiet -p bds-package-runtime -- sign-app-for-dmg",
        "{ path = \"bds-mcp\" }",
    ] {
        assert!(manifest.contains(required), "missing {required}");
    }
    assert!(!manifest.contains("signing-identity"));
    assert!(manifest.contains("authors.workspace = true"));

    let cargo_config = fs::read_to_string(Path::new(CRATE_DIR).join("../../.cargo/config.toml"))
        .expect("Cargo packaging aliases");
    for alias in ["bundle-macos", "bundle-windows", "bundle-linux"] {
        assert!(cargo_config.contains(alias), "missing cargo {alias}");
    }
    assert!(cargo_config.contains("prefer-dynamic"));
    assert!(cargo_config.contains("-mmacosx-version-min=26.0"));
    assert!(cargo_config.contains("link-arg=-Wl,-rpath,@executable_path\","));
    assert!(cargo_config.contains("@executable_path/../Resources"));
    assert!(cargo_config.contains("$ORIGIN:$ORIGIN/../lib/bds-ui"));

    let main = fs::read_to_string(Path::new(CRATE_DIR).join("src/main.rs")).unwrap();
    assert!(main.contains("assets/app-icons/bds.png"));

    let build = fs::read_to_string(Path::new(CRATE_DIR).join("build.rs")).unwrap();
    assert!(build.contains("set_icon(\"assets/app-icons/bds.ico\")"));

    let workspace_manifest =
        fs::read_to_string(Path::new(CRATE_DIR).join("../../Cargo.toml")).unwrap();
    assert!(workspace_manifest.contains("Georg Bauer <gb@rfc1437.de>"));
    assert!(workspace_manifest.contains("strip = \"symbols\""));
    assert!(workspace_manifest.contains("ort-download-binaries-rustls-tls"));
    assert!(workspace_manifest.contains("\"copy-dylibs\""));
    assert!(workspace_manifest.contains("\"sync-secret-service\", \"vendored\""));
    assert!(
        workspace_manifest
            .contains("libwebp-sys = { version = \"0.9.6\", default-features = false }")
    );

    let core_manifest =
        fs::read_to_string(Path::new(CRATE_DIR).join("../bds-core/Cargo.toml")).unwrap();
    assert!(core_manifest.contains("crate-type = [\"dylib\"]"));
    assert!(core_manifest.contains("[target.'cfg(target_os = \"windows\")'.dependencies]"));
    assert!(core_manifest.contains("libwebp-sys = { workspace = true, features = [\"sse41\"] }"));
    let core_build = fs::read_to_string(Path::new(CRATE_DIR).join("../bds-core/build.rs")).unwrap();
    assert!(core_build.contains("cargo:rustc-link-lib=framework=CoreML"));
    let server_manifest =
        fs::read_to_string(Path::new(CRATE_DIR).join("../bds-server/Cargo.toml")).unwrap();
    assert!(server_manifest.contains("crate-type = [\"dylib\"]"));
    assert!(!manifest.contains("{ path = \"bds-server\" }"));
    assert!(!manifest.contains("-p bds-server"));
    assert!(
        !Path::new(CRATE_DIR)
            .join("../bds-server/src/main.rs")
            .exists()
    );

    let packager =
        fs::read_to_string(Path::new(CRATE_DIR).join("../bds-package-runtime/src/main.rs"))
            .unwrap();
    assert!(packager.contains("Command::new(\"codesign\")"));
    assert!(packager.contains("CARGO_PACKAGER_FORMAT"));
    assert!(!packager.contains("--options"));
}

#[test]
fn tagged_releases_are_built_and_packaged_with_rust_tools() {
    let workspace = Path::new(CRATE_DIR).join("../..");
    let manifest = fs::read_to_string(workspace.join("Cargo.toml")).unwrap();
    assert!(manifest.contains("\"crates/bds-release\""));
    for dependency in [
        "apple-bundles = \"0.21\"",
        "apple-codesign = { version = \"0.29\", default-features = false }",
        "apple-dmg = \"0.5\"",
    ] {
        assert!(manifest.contains(dependency), "missing {dependency}");
    }

    let release_manifest =
        fs::read_to_string(workspace.join("crates/bds-release/Cargo.toml")).unwrap();
    for dependency in [
        "apple-bundles.workspace = true",
        "apple-codesign.workspace = true",
        "apple-dmg.workspace = true",
        "flate2",
        "tar",
        "zip",
    ] {
        assert!(
            release_manifest.contains(dependency),
            "release packager is missing {dependency}"
        );
    }

    let workflow = fs::read_to_string(workspace.join(".gitea/workflows/release.yml")).unwrap();
    serde_yaml::from_str::<serde_yaml::Value>(&workflow).expect("valid release workflow YAML");
    for required in [
        "tags:",
        "runs-on: linux-arm64",
        "cargo-zigbuild",
        "macos-linker-alias",
        "macos-x64-runtime",
        "ORT_LIB_LOCATION",
        "ORT_PREFER_DYNAMIC_LINK",
        "-Lnative=target/macos-linker",
        "-Wl,-rpath,@executable_path ",
        "-Wl,-undefined,dynamic_lookup",
        "aarch64-apple-darwin",
        "x86_64-apple-darwin",
        "aarch64-unknown-linux-gnu",
        "x86_64-unknown-linux-gnu",
        "cargo-xwin",
        "windows-runtime",
        "cargo +${RUST_TOOLCHAIN} xwin cache xwin",
        "/root/.cache/cargo-xwin/xwin/sdk/lib/um/x86_64",
        "-Lnative=target/x86_64-pc-windows-msvc/release",
        "apt-get install --no-install-recommends --yes nasm",
        "x86_64-pc-windows-msvc",
        "bds-release",
        "REBUILD_PUBLISHED_RELEASE: \"true\"",
        "prepare",
        "package",
        "publish",
        "PKG_CONFIG_ALLOW_CROSS",
        "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER",
        "CC_x86_64-unknown-linux-gnu=$PWD/.gitea/scripts/x86_64-linux-gnu-gcc-pic",
        "CXX_x86_64-unknown-linux-gnu=$PWD/.gitea/scripts/x86_64-linux-gnu-g++-pic",
        "unset ZSTD_SYS_USE_PKG_CONFIG",
    ] {
        assert!(
            workflow.contains(required),
            "release workflow is missing {required}"
        );
    }
    assert!(
        !workflow.contains("-Wl,-U,"),
        "Zig's Darwin linker does not support ld64's symbol-specific -U option"
    );
    assert!(
        !workflow.contains("ORT_LIB_PATH"),
        "ORT rc.11 expects its custom runtime in ORT_LIB_LOCATION"
    );
    for forbidden in ["python", "curl ", "hdiutil", "makensis", "tar -"] {
        assert!(
            !workflow.contains(forbidden),
            "release workflow must not use host tool {forbidden}"
        );
    }
    assert!(
        !workflow.contains("target-feature=+crt-static"),
        "Windows system dependencies must use the dynamic CRT"
    );
    assert!(
        !workflow.contains("run --release --locked --package bds-release"),
        "the release helper itself must stay unoptimized so cold runner jobs remain practical"
    );
    assert_eq!(
        workflow
            .matches("actions-rust-lang/setup-rust-toolchain@v1")
            .count(),
        4,
        "every release job using the Ubuntu runner image must bootstrap Rust"
    );
    assert_eq!(
        workflow
            .matches("needs: [prepare-release, build-linux-x64]")
            .count(),
        3,
        "the expensive platform builds must wait for the Linux x64 cross-build smoke test"
    );

    let linux_dependencies =
        fs::read_to_string(workspace.join(".gitea/scripts/install-linux-build-dependencies.sh"))
            .unwrap();
    for required in [
        "libwebkit2gtk-4.1-dev",
        "libgtk-3-dev",
        "libxdo-dev",
        "libwebkit2gtk-4.1-dev:amd64",
        "gcc-x86-64-linux-gnu",
        "Architectures: arm64",
        "--download-only",
        "dpkg-deb --extract",
        "libc.so.6 libm.so.6 libmvec.so.1",
        "cp -a \"$sysroot/usr/lib/x86_64-linux-gnu/$library\"",
        "cp -a \"$sysroot/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\"",
        "/usr/share/pkgconfig/.",
        "/opt/ruds-x64-sysroot",
    ] {
        assert!(
            linux_dependencies.contains(required),
            "Linux dependency setup is missing {required}"
        );
    }
    let multiarch = linux_dependencies
        .find("dpkg --add-architecture amd64")
        .expect("amd64 architecture setup");
    assert!(
        !linux_dependencies.contains("apt-get remove"),
        "cross-build setup must not replace native runner packages"
    );
    let native_source_filter = linux_dependencies
        .find("'/^Architectures:/d; /^Types: deb$/a Architectures: arm64'")
        .expect("native apt architecture filter");
    let native_cross_compiler = linux_dependencies
        .find("install_packages build-essential cmake gcc-x86-64-linux-gnu")
        .expect("native cross compiler setup");
    assert!(
        linux_dependencies[native_source_filter..native_cross_compiler]
            .contains("sudo apt-get update"),
        "native apt indexes must be refreshed after constraining the source architecture"
    );
    assert!(
        native_cross_compiler < multiarch,
        "the native cross compiler must be installed before enabling multiarch"
    );
    assert!(
        linux_dependencies.find("--download-only").unwrap() > multiarch,
        "foreign libraries must only be downloaded after enabling multiarch"
    );
    assert!(workflow.contains("PKG_CONFIG_SYSROOT_DIR: /opt/ruds-x64-sysroot"));
    assert!(
        workflow.contains("link-arg=--sysroot=/opt/ruds-x64-sysroot"),
        "the cross-linker must resolve absolute libc linker-script paths inside the x64 sysroot"
    );
    assert!(!workflow.contains("export CC=\""));
    assert!(!workflow.contains("export CXX=\""));

    let workspace_manifest = fs::read_to_string(workspace.join("Cargo.toml")).unwrap();
    let core_manifest = fs::read_to_string(workspace.join("crates/bds-core/Cargo.toml")).unwrap();
    assert!(
        workspace_manifest.contains("pagefind = { version = \"1.5.2\", default-features = false }"),
        "Pagefind's unused Actix serving stack must stay disabled"
    );
    assert!(
        workspace_manifest.contains("usearch = { version = \"2.26\", default-features = false }"),
        "USearch SIMD backends must be selected explicitly per target"
    );
    assert!(
        core_manifest.contains("[target.'cfg(target_os = \"macos\")'.dependencies]")
            && core_manifest.contains("[target.'cfg(not(target_os = \"macos\"))'.dependencies]")
            && core_manifest.contains("usearch = { workspace = true, features = [\"numkong\"] }"),
        "macOS must use portable USearch while native targets keep NumKong SIMD"
    );

    let release_source =
        fs::read_to_string(workspace.join("crates/bds-release/src/packaging.rs")).unwrap();
    assert!(release_source.contains("LC_ID_DYLIB"));
    assert!(release_source.contains("rewrite_macho_rpaths"));
    assert!(!release_source.contains("Linux releases contain the portable CLI tools"));
}

#[test]
fn x64_cross_compiler_wrappers_append_pic_last() {
    for (script, compiler) in [
        ("x86_64-linux-gnu-gcc-pic", "x86_64-linux-gnu-gcc"),
        ("x86_64-linux-gnu-g++-pic", "x86_64-linux-gnu-g++"),
    ] {
        let contents = fs::read_to_string(
            Path::new(CRATE_DIR)
                .join("../../.gitea/scripts")
                .join(script),
        )
        .unwrap();
        assert_eq!(
            contents,
            format!("#!/bin/sh\nexec {compiler} \"$@\" -fPIC\n")
        );
    }
}
