import Foundation
import Testing

@testable import NucleicCore

@Suite("GitHub credentials")
struct GitHubCredentialsTests {

    /// A fresh temp dir to stand in for a session's bind-mounted claude-home.
    private func tempHome() -> String {
        let dir = (NSTemporaryDirectory() as NSString)
            .appendingPathComponent("nucleic-ghcreds-\(UUID().uuidString)")
        try? FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)
        return dir
    }

    // MARK: - none / inactive

    @Test func noneModeProvisionsNothing() {
        let home = tempHome()
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .none), hostHomeDir: home, containerHomeDir: home)
        #expect(env.isEmpty)
    }

    @Test func selectedModeWithoutSecretIsInactive() {
        let home = tempHome()
        let token = GitHubCredentialConfig(mode: .token, token: nil)
        #expect(!token.isActive)
        #expect(GitHubCredentialProvisioner.provision(token, hostHomeDir: home, containerHomeDir: home).isEmpty)

        let ssh = GitHubCredentialConfig(mode: .ssh, sshPrivateKey: "")
        #expect(!ssh.isActive)
        #expect(GitHubCredentialProvisioner.provision(ssh, hostHomeDir: home, containerHomeDir: home).isEmpty)
    }

    // MARK: - token (HTTPS)

    @Test func tokenWritesCredentialFileAndSetsEnv() throws {
        let home = tempHome()
        // Container path differs from host path (per-session container mounts home at /root/.claude).
        let containerHome = "/root/.claude"
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .token, token: "ghp_secret123"),
            hostHomeDir: home, containerHomeDir: containerHome)

        // gh authenticates from the environment.
        #expect(env["GITHUB_TOKEN"] == "ghp_secret123")
        #expect(env["GH_TOKEN"] == "ghp_secret123")

        // git credential.helper points the store helper at the container-visible path.
        #expect(env["GIT_CONFIG_COUNT"] == "1")
        #expect(env["GIT_CONFIG_KEY_0"] == "credential.helper")
        let containerCredPath = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.credentialsFile)
        #expect(env["GIT_CONFIG_VALUE_0"] == "store --file=\(containerCredPath)")

        // The secret file is written host-side at the host path, 0600, with the token in it.
        let hostCredPath = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.credentialsFile)
        let contents = try String(contentsOfFile: hostCredPath, encoding: .utf8)
        #expect(contents == "https://x-access-token:ghp_secret123@github.com\n")
        let perms = try FileManager.default.attributesOfItem(atPath: hostCredPath)[.posixPermissions] as? Int
        #expect(perms == 0o600)

        // The token is never used as an SSH command.
        #expect(env["GIT_SSH_COMMAND"] == nil)
    }

    // MARK: - SSH

    private let fakeKey = """
        -----BEGIN OPENSSH PRIVATE KEY-----
        b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
        -----END OPENSSH PRIVATE KEY-----
        """

    @Test func sshWritesKeyAndSetsSSHCommand() throws {
        let home = tempHome()
        let containerHome = "/root/.claude"
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey, signCommits: false),
            hostHomeDir: home, containerHomeDir: containerHome)

        let containerKey = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshKeyFile)
        let known = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.knownHostsFile)
        let cmd = try #require(env["GIT_SSH_COMMAND"])
        // Paths are single-quoted so a home dir with spaces survives git's shell word-split.
        #expect(cmd.contains("ssh -i '\(containerKey)'"))
        #expect(cmd.contains("IdentitiesOnly=yes"))
        #expect(cmd.contains("StrictHostKeyChecking=accept-new"))
        #expect(cmd.contains("UserKnownHostsFile='\(known)'"))

        // Key written 0600 with a trailing newline appended.
        let hostKey = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshKeyFile)
        let written = try String(contentsOfFile: hostKey, encoding: .utf8)
        #expect(written == fakeKey + "\n")
        let perms = try FileManager.default.attributesOfItem(atPath: hostKey)[.posixPermissions] as? Int
        #expect(perms == 0o600)

        // Signing off → no gpg/commit config injected.
        #expect(env["GIT_CONFIG_COUNT"] == nil)
        // Token-only env never appears for SSH mode.
        #expect(env["GITHUB_TOKEN"] == nil)
    }

    @Test func sshSigningInjectsGitConfig() throws {
        let home = tempHome()
        let containerHome = home  // shared control container: home mounted at its identical path
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey, signCommits: true),
            hostHomeDir: home, containerHomeDir: containerHome)

        let containerKey = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshKeyFile)
        // Collect the injected git config into a dict regardless of ordering.
        let count = try #require(env["GIT_CONFIG_COUNT"].flatMap { Int($0) })
        var cfg: [String: String] = [:]
        for i in 0..<count {
            let k = try #require(env["GIT_CONFIG_KEY_\(i)"])
            let v = try #require(env["GIT_CONFIG_VALUE_\(i)"])
            cfg[k] = v
        }
        #expect(cfg["gpg.format"] == "ssh")
        // Signer pinned to OpenSSH so this env is safe host-side too (a global 1Password
        // `gpg.ssh.program` would otherwise hijack signing and reject the key).
        #expect(cfg["gpg.ssh.program"] == "/usr/bin/ssh-keygen")
        #expect(cfg["user.signingkey"] == containerKey)
        #expect(cfg["commit.gpgsign"] == "true")
        #expect(cfg["tag.gpgsign"] == "true")
    }

    @Test func gitForceSSHInjectsInsteadOfRewrite() throws {
        let home = tempHome()
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey, gitForceSSH: true),
            hostHomeDir: home, containerHomeDir: home)
        let count = try #require(env["GIT_CONFIG_COUNT"].flatMap { Int($0) })
        var cfg: [String: String] = [:]
        for i in 0..<count {
            cfg[try #require(env["GIT_CONFIG_KEY_\(i)"])] = try #require(env["GIT_CONFIG_VALUE_\(i)"])
        }
        #expect(cfg["url.git@github.com:.insteadOf"] == "https://github.com/")
        // No gh config dir unless ghForceSSH is set.
        #expect(env["GH_CONFIG_DIR"] == nil)
    }

    @Test func ghForceSSHWritesConfigAndPointsGhConfigDir() throws {
        let home = tempHome()
        let containerHome = "/root/.claude"
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey, ghForceSSH: true),
            hostHomeDir: home, containerHomeDir: containerHome)

        // GH_CONFIG_DIR points at the container-visible dir; the file is written host-side.
        let containerGhDir = (containerHome as NSString).appendingPathComponent(GitHubCredentialProvisioner.ghConfigDir)
        #expect(env["GH_CONFIG_DIR"] == containerGhDir)

        let hostConfig = ((home as NSString).appendingPathComponent(GitHubCredentialProvisioner.ghConfigDir) as NSString)
            .appendingPathComponent("config.yml")
        let contents = try String(contentsOfFile: hostConfig, encoding: .utf8)
        #expect(contents.contains("git_protocol: ssh"))
    }

    @Test func sshModeWithTokenAuthenticatesGhApiButKeepsGitOnSSH() throws {
        let home = tempHome()
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, token: "ghp_api", sshPrivateKey: fakeKey),
            hostHomeDir: home, containerHomeDir: home)

        // gh's API gets the token...
        #expect(env["GH_TOKEN"] == "ghp_api")
        #expect(env["GITHUB_TOKEN"] == "ghp_api")
        // ...while git still transports over SSH and no HTTPS credential helper is configured.
        #expect(env["GIT_SSH_COMMAND"] != nil)
        let count = Int(env["GIT_CONFIG_COUNT"] ?? "0") ?? 0
        for i in 0..<count {
            #expect(env["GIT_CONFIG_KEY_\(i)"] != "credential.helper")
        }
        // No HTTPS credentials file is written in SSH mode (git can't fall back to token-over-HTTPS).
        let credPath = (home as NSString).appendingPathComponent(GitHubCredentialProvisioner.credentialsFile)
        #expect(!FileManager.default.fileExists(atPath: credPath))
    }

    @Test func sshModeWithoutTokenSetsNoGhToken() {
        let home = tempHome()
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey),
            hostHomeDir: home, containerHomeDir: home)
        #expect(env["GH_TOKEN"] == nil)
        #expect(env["GITHUB_TOKEN"] == nil)
        #expect(env["GIT_SSH_COMMAND"] != nil)
    }

    @Test func sshForceFlagsAreIgnoredOutsideSSHMode() {
        let home = tempHome()
        // In token mode the SSH-only force flags are no-ops (no insteadOf, no GH_CONFIG_DIR).
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .token, token: "ghp_x", ghForceSSH: true, gitForceSSH: true),
            hostHomeDir: home, containerHomeDir: home)
        #expect(env["GH_CONFIG_DIR"] == nil)
        #expect(env["GIT_CONFIG_COUNT"] == "1")  // only the credential.helper
        #expect(env["GIT_CONFIG_KEY_0"] == "credential.helper")
    }

    @Test func keyGetsTrailingNewlineOnlyWhenMissing() throws {
        let home = tempHome()
        let withNL = fakeKey + "\n"
        _ = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: withNL),
            hostHomeDir: home, containerHomeDir: home)
        let hostKey = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshKeyFile)
        let written = try String(contentsOfFile: hostKey, encoding: .utf8)
        #expect(written == withNL)  // not double-newlined
    }

    // MARK: - SSH passphrase (askpass)

    @Test func passphraseWritesAskpassHelperAndForcesIt() throws {
        let home = tempHome()
        let containerHome = "/root/.claude"
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey, sshPassphrase: "s3 cret"),
            hostHomeDir: home, containerHomeDir: containerHome)

        // OpenSSH is forced onto the askpass helper (the container-visible path).
        #expect(env["SSH_ASKPASS_REQUIRE"] == "force")
        let containerAskpass = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshAskpassFile)
        #expect(env["SSH_ASKPASS"] == containerAskpass)

        // Passphrase file written host-side, 0600, with a single trailing newline.
        let hostPass = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshPassphraseFile)
        #expect(try String(contentsOfFile: hostPass, encoding: .utf8) == "s3 cret\n")
        let passPerms = try FileManager.default.attributesOfItem(atPath: hostPass)[.posixPermissions] as? Int
        #expect(passPerms == 0o600)

        // Askpass script written host-side, 0700 (executable), printing the container passphrase path.
        let hostAskpass = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshAskpassFile)
        let script = try String(contentsOfFile: hostAskpass, encoding: .utf8)
        let containerPass = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshPassphraseFile)
        #expect(script.contains(containerPass))
        let scriptPerms = try FileManager.default.attributesOfItem(atPath: hostAskpass)[.posixPermissions] as? Int
        #expect(scriptPerms == 0o700)
    }

    @Test func unencryptedKeyConfiguresNoAskpass() {
        let home = tempHome()
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .ssh, sshPrivateKey: fakeKey),
            hostHomeDir: home, containerHomeDir: home)
        #expect(env["SSH_ASKPASS"] == nil)
        #expect(env["SSH_ASKPASS_REQUIRE"] == nil)
        let hostPass = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshPassphraseFile)
        #expect(!FileManager.default.fileExists(atPath: hostPass))
    }

    // MARK: - Nucleic-managed key

    @Test func managedModeProvisionsLikeSSH() throws {
        let home = tempHome()
        let containerHome = "/root/.claude"
        // A managed key arrives as `sshPrivateKey` with no passphrase; it must provision exactly
        // like a user SSH key — same GIT_SSH_COMMAND and signing config.
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey, signCommits: true),
            hostHomeDir: home, containerHomeDir: containerHome)

        let containerKey = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshKeyFile)
        #expect(try #require(env["GIT_SSH_COMMAND"]).contains("ssh -i '\(containerKey)'"))
        // No askpass — managed keys are passphrase-free.
        #expect(env["SSH_ASKPASS"] == nil)

        var cfg: [String: String] = [:]
        let count = try #require(env["GIT_CONFIG_COUNT"].flatMap { Int($0) })
        for i in 0..<count {
            cfg[try #require(env["GIT_CONFIG_KEY_\(i)"])] = try #require(env["GIT_CONFIG_VALUE_\(i)"])
        }
        #expect(cfg["gpg.format"] == "ssh")
        #expect(cfg["user.signingkey"] == containerKey)

        let hostKey = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshKeyFile)
        #expect(FileManager.default.fileExists(atPath: hostKey))
    }

    @Test func managedModeWithoutKeyIsInactive() {
        let home = tempHome()
        let cfg = GitHubCredentialConfig(mode: .managed, sshPrivateKey: nil)
        #expect(!cfg.isActive)
        #expect(GitHubCredentialProvisioner.provision(
            cfg, hostHomeDir: home, containerHomeDir: home).isEmpty)
    }

    // MARK: - Host-side provisioning (clone / push, not a sandbox)

    @Test func provisionHostPinsKeyAndShredsItOnCleanup() throws {
        // A host-side GitHub op must use the configured key only — never the user's ~/.ssh keys —
        // and never contact the keychain-backed ssh-agent.
        let (env, cleanup) = GitHubCredentialProvisioner.provisionHost(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey))
        let cmd = try #require(env["GIT_SSH_COMMAND"])
        #expect(cmd.contains("IdentitiesOnly=yes"))
        // Hermetic from the user's ssh setup: no agent (covers a config `IdentityAgent` and an
        // inherited `SSH_AUTH_SOCK`), and the user/system ssh_config ignored entirely (covers a
        // `Host * IdentityAgent`/`IdentityFile`, e.g. 1Password's agent).
        #expect(cmd.contains("IdentityAgent=none"))
        #expect(cmd.contains("-F /dev/null"))

        // The `-i <path>` key is written into a private temp dir, 0600… (path is single-quoted in
        // the command, so pull it out from between the quotes — robust even if it contained spaces).
        let afterDashI = try #require(cmd.range(of: "-i '")).upperBound
        let keyPath = String(cmd[afterDashI...].prefix { $0 != "'" })
        #expect(FileManager.default.fileExists(atPath: keyPath))
        let perms = try FileManager.default.attributesOfItem(atPath: keyPath)[.posixPermissions] as? Int
        #expect(perms == 0o600)

        // …and cleanup shreds it, so the private key never lingers on disk.
        cleanup()
        #expect(!FileManager.default.fileExists(atPath: keyPath))
    }

    @Test func provisionHostInactiveProvisionsNothing() {
        let (env, cleanup) = GitHubCredentialProvisioner.provisionHost(
            GitHubCredentialConfig(mode: .none))
        #expect(env.isEmpty)
        cleanup()  // no-op; must not crash
    }

    // MARK: - Extra SSH config (custom Hostname/Port/ProxyCommand)

    @Test func sshConfigWritesFileAndPointsSSHAtIt() throws {
        let home = tempHome()
        let containerHome = "/root/.claude"
        let cfg = "Host github.com\n  Hostname ssh.github.com\n  Port 443"
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey, sshConfig: cfg),
            hostHomeDir: home, containerHomeDir: containerHome)

        let cmd = try #require(env["GIT_SSH_COMMAND"])
        // Identity/agent stay pinned on the command line…
        #expect(cmd.contains("IdentitiesOnly=yes"))
        #expect(cmd.contains("IdentityAgent=none"))
        // …and ssh is pointed at the user's config (container path), honoring its connectivity.
        let containerConfig = (containerHome as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshConfigFile)
        #expect(cmd.contains("-F '\(containerConfig)'"))

        // The config is written host-side, 0600, with a single trailing newline.
        let hostConfig = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshConfigFile)
        #expect(try String(contentsOfFile: hostConfig, encoding: .utf8) == cfg + "\n")
        let perms = try FileManager.default.attributesOfItem(atPath: hostConfig)[.posixPermissions] as? Int
        #expect(perms == 0o600)
    }

    @Test func noSSHConfigPinsDevNullAndWritesNoFile() {
        let home = tempHome()
        let env = GitHubCredentialProvisioner.provision(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey),
            hostHomeDir: home, containerHomeDir: home)
        // With no user ssh config, the sandbox command is hermetic: `-F /dev/null` so ssh ignores the
        // container's `/etc/ssh/ssh_config` and any `~/.ssh/config` a tool/agent might write — which
        // could otherwise carry a `Host github.com IdentityFile` that diverts the push off the key.
        #expect(env["GIT_SSH_COMMAND"]?.contains(" -F /dev/null") == true)
        #expect(env["GIT_SSH_COMMAND"]?.contains("IdentitiesOnly=yes") == true)
        // No user-config file is written when none was supplied.
        let hostConfig = (home as NSString)
            .appendingPathComponent(GitHubCredentialProvisioner.sshConfigFile)
        #expect(!FileManager.default.fileExists(atPath: hostConfig))
    }

    @Test func provisionHostWithSSHConfigHonorsItInsteadOfDevNull() throws {
        let cfg = "Host github.com\n  Port 443"
        let (env, cleanup) = GitHubCredentialProvisioner.provisionHost(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey, sshConfig: cfg))
        defer { cleanup() }
        let cmd = try #require(env["GIT_SSH_COMMAND"])
        // The user's config file is used (in the temp dir); the hermetic /dev/null fallback is not,
        // and the agent is still pinned off.
        #expect(cmd.contains(" -F "))
        #expect(cmd.contains(GitHubCredentialProvisioner.sshConfigFile))
        #expect(!cmd.contains("/dev/null"))
        #expect(cmd.contains("IdentityAgent=none"))
    }

    // MARK: - Host-side commit signing (promote / integration commits)

    @Test func commitSigningOffReturnsNothing() {
        // signCommits off, or a non-SSH mode, ⇒ no signing flags so the commit runs as before.
        let (flags, env, cleanup) = GitHubCredentialProvisioner.commitSigning(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey, signCommits: false))
        #expect(flags.isEmpty)
        #expect(env.isEmpty)
        cleanup()  // no-op; must not crash

        let (tokenFlags, _, tokenCleanup) = GitHubCredentialProvisioner.commitSigning(
            GitHubCredentialConfig(mode: .token, token: "t", signCommits: true))
        #expect(tokenFlags.isEmpty)  // token mode has no signing key
        tokenCleanup()
    }

    @Test func commitSigningWritesKeyAndReturnsSSHSigningFlags() throws {
        let (flags, env, cleanup) = GitHubCredentialProvisioner.commitSigning(
            GitHubCredentialConfig(mode: .managed, sshPrivateKey: fakeKey, signCommits: true))
        // SSH-format signing with commit + tag signing forced on.
        #expect(flags.contains("gpg.format=ssh"))
        #expect(flags.contains("commit.gpgsign=true"))
        #expect(flags.contains("tag.gpgsign=true"))
        // The signer is pinned to OpenSSH, hermetic from a user's 1Password `gpg.ssh.program`
        // (which would otherwise hijack signing and reject the Managed Git key).
        #expect(flags.contains("gpg.ssh.program=/usr/bin/ssh-keygen"))
        // A managed key carries no passphrase, so no askpass env is needed.
        #expect(env.isEmpty)

        // user.signingkey points at the private key, written into a temp dir 0600…
        let signingKeyArg = try #require(flags.first { $0.hasPrefix("user.signingkey=") })
        let keyPath = String(signingKeyArg.dropFirst("user.signingkey=".count))
        #expect(FileManager.default.fileExists(atPath: keyPath))
        let perms = try FileManager.default.attributesOfItem(atPath: keyPath)[.posixPermissions] as? Int
        #expect(perms == 0o600)

        // …and cleanup shreds it, so the private key never lingers on disk.
        cleanup()
        #expect(!FileManager.default.fileExists(atPath: keyPath))
    }

    @Test func commitSigningWithPassphraseAddsAskpass() throws {
        // A passphrase-protected user `.ssh` key gets an askpass helper so headless signing never
        // blocks on a prompt (managed keys are passphrase-free, so this only applies to `.ssh`).
        let (flags, env, cleanup) = GitHubCredentialProvisioner.commitSigning(
            GitHubCredentialConfig(
                mode: .ssh, sshPrivateKey: fakeKey, sshPassphrase: "secret", signCommits: true))
        defer { cleanup() }
        #expect(flags.contains("gpg.format=ssh"))
        let askpass = try #require(env["SSH_ASKPASS"])
        #expect(env["SSH_ASKPASS_REQUIRE"] == "force")
        #expect(FileManager.default.fileExists(atPath: askpass))
    }
}
