import Foundation
import Testing

@testable import NucleicCore

/// Unit tests for the `LoginShellEnv` snapshot (docs/NASH.md §7.7.2, §10.6): the diff/filter is
/// pure, and resolution uses an injectable runner — no real shell is ever spawned here.
@Suite struct LoginShellEnvTests {

    // MARK: - Parse (sentinel-fenced `env -0`)

    @Test func parseExtractsFencedNulDelimitedEntries() {
        let payload =
            "rc banner noise\n___NUCLEIC_LOGIN_ENV_BEGIN___"
            + "HOMEBREW_PREFIX=/opt/homebrew\0NVM_DIR=/Users/u/.nvm\0MULTI=line1\nline2\0IGNORED"
            + "___NUCLEIC_LOGIN_ENV_END___trailing noise"
        let env = LoginShellEnv.parse(output: payload)
        #expect(env?["HOMEBREW_PREFIX"] == "/opt/homebrew")
        #expect(env?["NVM_DIR"] == "/Users/u/.nvm")
        #expect(env?["MULTI"] == "line1\nline2")  // env -0 exists exactly for value newlines
        #expect(env?["IGNORED"] == nil)  // no `=` → not an assignment
    }

    @Test func parseFailsWithoutSentinelsOrContent() {
        #expect(LoginShellEnv.parse(output: "no sentinels here") == nil)
        #expect(
            LoginShellEnv.parse(
                output: "___NUCLEIC_LOGIN_ENV_BEGIN______NUCLEIC_LOGIN_ENV_END___") == nil)
    }

    // MARK: - Delta + filter (§7.7.2: PATH/noise/credentials dropped; identical values dropped)

    @Test func deltaKeepsOnlyNewToolchainStateAndFiltersNoise() {
        let snapshot = [
            "HOMEBREW_PREFIX": "/opt/homebrew",  // new → kept
            "JAVA_HOME": "/opt/jdk",  // new → kept
            "HOME": "/Users/u",  // excluded key
            "PATH": "/opt/homebrew/bin",  // PATH rides LoginShellPATH, never here
            "SHLVL": "2", "PWD": "/", "OLDPWD": "/tmp", "_": "/usr/bin/env",  // shell noise
            "ZSH_VERSION": "5.9",  // excluded prefix
            "__CF_USER_TEXT_ENCODING": "0x0",  // excluded prefix
            "SAME": "unchanged",  // identical to app env → no signal
            "ANTHROPIC_API_KEY": "sk-secret",  // credential-shaped → dropped
            "GITHUB_TOKEN": "ghp_x",  // credential-shaped → dropped
        ]
        let app = ["SAME": "unchanged", "HOME": "/Users/u"]
        let delta = LoginShellEnv.delta(snapshot: snapshot, appEnvironment: app)
        #expect(delta == ["HOMEBREW_PREFIX": "/opt/homebrew", "JAVA_HOME": "/opt/jdk"])
    }

    @Test func deltaKeepsAValueTheLoginShellOverrides() {
        // Not just new keys: a key whose login-shell value DIFFERS from the app's is signal too
        // (e.g. the user's rc bumps JAVA_HOME) — but merged() below still lets the app env win,
        // so the override only lands where the composed env didn't already carry the key.
        let delta = LoginShellEnv.delta(
            snapshot: ["JAVA_HOME": "/opt/jdk21"], appEnvironment: ["JAVA_HOME": "/opt/jdk17"])
        #expect(delta == ["JAVA_HOME": "/opt/jdk21"])
    }

    // MARK: - Merge (underlay: the composed env always wins)

    @Test func mergedNeverOverwritesExistingKeys() {
        let out = LoginShellEnv.merged(
            delta: ["NVM_DIR": "/nvm", "GIT_SSH_COMMAND": "ssh -i evil"],
            into: ["GIT_SSH_COMMAND": "ssh -i managed-key"])  // the Managed Git pin
        #expect(out["NVM_DIR"] == "/nvm")
        #expect(out["GIT_SSH_COMMAND"] == "ssh -i managed-key")  // pin survives (§10.6)
    }

    // MARK: - Resolution (stub runner; no real shell)

    @Test func resolveRunsLoginInteractiveShellAndParses() {
        let env = LoginShellEnv.resolve(
            shell: "/bin/fakesh",
            runner: { shellPath, args in
                #expect(shellPath == "/bin/fakesh")
                #expect(args.first == "-ilc")  // login + interactive, like LoginShellPATH
                return (
                    0,
                    "___NUCLEIC_LOGIN_ENV_BEGIN___A=1\0B=2___NUCLEIC_LOGIN_ENV_END___"
                )
            })
        #expect(env == ["A": "1", "B": "2"])
    }

    @Test func resolveFailsClosedOnBadShellOutcome() {
        #expect(LoginShellEnv.resolve(shell: "/bin/zsh", runner: { _, _ in nil }) == nil)
        #expect(LoginShellEnv.resolve(shell: "/bin/zsh", runner: { _, _ in (1, "x") }) == nil)
        #expect(LoginShellEnv.resolve(shell: "/bin/zsh", runner: { _, _ in (0, "no fence") }) == nil)
    }
}
