import Foundation
import Testing

@testable import NucleicCore

@Suite struct LoginShellPATHTests {
    /// The login-shell PATH is placed FIRST (the user's real ordering, where `claude` actually
    /// resolves), and the well-known install dirs — led by `~/.claude/local` — are always appended,
    /// so the GUI-launch-missing `claude` is found even when the inherited PATH lacks its directory.
    @Test func mergesLoginPathFirstThenWellKnownDirs() {
        let merged = LoginShellPATH.merged(
            loginPATH: "/Users/me/.claude/local:/opt/homebrew/bin:/usr/bin",
            currentPATH: "/usr/bin:/bin:/usr/sbin:/sbin",  // launchd's minimal PATH
            home: "/Users/me")
        let dirs = merged.split(separator: ":").map(String.init)

        // Login PATH leads.
        #expect(dirs.first == "/Users/me/.claude/local")
        // The inherited entries are preserved (additive, never dropped).
        #expect(dirs.contains("/sbin"))
        // Well-known dirs are present as the safety net.
        #expect(dirs.contains("/Users/me/.local/bin"))
        #expect(dirs.contains("/Users/me/.npm-global/bin"))
    }

    /// No duplicate or empty segments survive, regardless of overlap between the three sources.
    @Test func dropsDuplicatesAndEmptySegments() {
        let merged = LoginShellPATH.merged(
            loginPATH: "/usr/local/bin::/usr/bin",  // stray empty segment
            currentPATH: "/usr/bin:/usr/local/bin",  // overlaps login PATH
            home: "/Users/me")
        let dirs = merged.split(separator: ":", omittingEmptySubsequences: false).map(String.init)

        #expect(!dirs.contains(""))
        #expect(dirs.filter { $0 == "/usr/bin" }.count == 1)
        #expect(dirs.filter { $0 == "/usr/local/bin" }.count == 1)
    }

    /// When login-shell resolution fails (`nil`), the inherited PATH plus the well-known dirs still
    /// carry `~/.claude/local`, so a GUI launch can find the native-installer `claude` regardless.
    @Test func fallsBackToWellKnownDirsWhenLoginResolutionFails() {
        let merged = LoginShellPATH.merged(
            loginPATH: nil, currentPATH: "/usr/bin:/bin", home: "/Users/me")
        let dirs = merged.split(separator: ":").map(String.init)

        #expect(dirs.contains("/usr/bin"))
        #expect(dirs.contains("/Users/me/.claude/local"))
    }

    /// `resolve` parses the PATH fenced between the sentinels, ignoring any rc-file banner noise
    /// printed around it, and feeds the chosen shell a login+interactive invocation.
    @Test func resolveExtractsFencedPathAndUsesLoginInteractiveShell() {
        var seenShell: String?
        var seenArgs: [String]?
        let resolved = LoginShellPATH.resolve(shell: "/bin/zsh") { shell, args in
            seenShell = shell
            seenArgs = args
            // Simulate an rc that prints a banner before the fenced PATH.
            return (
                0,
                "Welcome!\n___NUCLEIC_LOGIN_PATH_BEGIN___/opt/homebrew/bin:/usr/bin___NUCLEIC_LOGIN_PATH_END___"
            )
        }

        #expect(resolved == "/opt/homebrew/bin:/usr/bin")
        #expect(seenShell == "/bin/zsh")
        #expect(seenArgs?.first == "-ilc")
    }

    /// A nonzero shell exit yields no PATH (caller degrades to the inherited PATH + well-known dirs).
    @Test func resolveReturnsNilOnNonzeroExit() {
        let resolved = LoginShellPATH.resolve(shell: "/bin/zsh") { _, _ in (1, "anything") }
        #expect(resolved == nil)
    }

    /// An empty `$SHELL` falls back to `/bin/zsh` (the macOS default) rather than spawning "".
    @Test func resolveDefaultsToZshWhenShellEnvEmpty() {
        var seenShell: String?
        _ = LoginShellPATH.resolve(shell: "") { shell, _ in
            seenShell = shell
            return nil
        }
        #expect(seenShell == "/bin/zsh")
    }

    /// `augment` widens an environment's PATH in place while leaving its other keys untouched.
    @Test func augmentWidensPathAndKeepsOtherKeys() {
        let augmented = LoginShellPATH.augment(["PATH": "/usr/bin", "FOO": "bar"])
        #expect(augmented["FOO"] == "bar")
        let dirs = (augmented["PATH"] ?? "").split(separator: ":").map(String.init)
        #expect(dirs.contains("/usr/bin"))
        // The well-known Claude install dir is always added.
        #expect(dirs.contains { $0.hasSuffix("/.claude/local") })
    }
}
