import Foundation

struct GeminiTestEnvironment {
    enum GeminiCLILayout {
        case npmNested
        case nixShare
        case fnmBundle
        case homebrewBundle
    }

    let homeURL: URL
    private let geminiDir: URL
    private let antigravityDir: URL

    init() throws {
        let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
        let geminiDir = root.appendingPathComponent(".gemini")
        try FileManager.default.createDirectory(at: geminiDir, withIntermediateDirectories: true)
        let antigravityDir = root
            .appendingPathComponent(".codexbar")
            .appendingPathComponent("antigravity")
        try FileManager.default.createDirectory(at: antigravityDir, withIntermediateDirectories: true)
        self.homeURL = root
        self.geminiDir = geminiDir
        self.antigravityDir = antigravityDir
    }

    func cleanup() {
        try? FileManager.default.removeItem(at: self.homeURL)
    }

    func writeSettings(authType: String) throws {
        let payload: [String: Any] = [
            "security": [
                "auth": [
                    "selectedType": authType,
                ],
            ],
        ]
        let data = try JSONSerialization.data(withJSONObject: payload)
        try data.write(to: self.geminiDir.appendingPathComponent("settings.json"), options: .atomic)
    }

    func writeCredentials(
        accessToken: String?,
        refreshToken: String?,
        expiry: Date,
        idToken: String?) throws
    {
        var payload: [String: Any] = [
            "expiry_date": expiry.timeIntervalSince1970 * 1000,
        ]
        if let accessToken { payload["access_token"] = accessToken }
        if let refreshToken { payload["refresh_token"] = refreshToken }
        if let idToken { payload["id_token"] = idToken }
        let data = try JSONSerialization.data(withJSONObject: payload)
        try data.write(to: self.geminiDir.appendingPathComponent("oauth_creds.json"), options: .atomic)
    }

    func readCredentials() throws -> [String: Any] {
        let url = self.geminiDir.appendingPathComponent("oauth_creds.json")
        let data = try Data(contentsOf: url)
        let object = try JSONSerialization.jsonObject(with: data)
        return object as? [String: Any] ?? [:]
    }

    func writeAntigravityCredentials(
        accessToken: String,
        refreshToken: String?,
        expiry: Date,
        idToken: String? = nil,
        email: String? = nil,
        projectID: String? = nil,
        clientID: String? = nil,
        clientSecret: String? = nil) throws
    {
        var payload: [String: Any] = [
            "access_token": accessToken,
            "expiry_date": expiry.timeIntervalSince1970 * 1000,
        ]
        if let refreshToken { payload["refresh_token"] = refreshToken }
        if let idToken { payload["id_token"] = idToken }
        if let email { payload["email"] = email }
        if let projectID { payload["project_id"] = projectID }
        if let clientID { payload["client_id"] = clientID }
        if let clientSecret { payload["client_secret"] = clientSecret }
        let data = try JSONSerialization.data(withJSONObject: payload)
        try data.write(to: self.antigravityDir.appendingPathComponent("oauth_creds.json"), options: .atomic)
    }

    func readAntigravityCredentials() throws -> [String: Any] {
        let url = self.antigravityDir.appendingPathComponent("oauth_creds.json")
        let data = try Data(contentsOf: url)
        let object = try JSONSerialization.jsonObject(with: data)
        return object as? [String: Any] ?? [:]
    }

    func writeFakeGeminiCLI(includeOAuth: Bool = true, layout: GeminiCLILayout = .npmNested) throws -> URL {
        let base = self.homeURL.appendingPathComponent("gemini-cli")
        let binDir = base.appendingPathComponent("bin")
        try FileManager.default.createDirectory(at: binDir, withIntermediateDirectories: true)

        switch layout {
        case .npmNested:
            let oauthPath = base
                .appendingPathComponent("lib")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli-core")
                .appendingPathComponent("dist")
                .appendingPathComponent("src")
                .appendingPathComponent("code_assist")
                .appendingPathComponent("oauth2.js")

            if includeOAuth {
                try FileManager.default.createDirectory(
                    at: oauthPath.deletingLastPathComponent(),
                    withIntermediateDirectories: true)

                let oauthContent = """
                const OAUTH_CLIENT_ID = 'test-client-id';
                const OAUTH_CLIENT_SECRET = 'test-client-secret';
                """
                try oauthContent.write(to: oauthPath, atomically: true, encoding: .utf8)
            }

            let geminiBinary = binDir.appendingPathComponent("gemini")
            try "#!/bin/bash\nexit 0\n".write(to: geminiBinary, atomically: true, encoding: .utf8)
            try FileManager.default.setAttributes(
                [.posixPermissions: 0o755],
                ofItemAtPath: geminiBinary.path)
            return geminiBinary

        case .nixShare:
            let oauthPath = base
                .appendingPathComponent("share")
                .appendingPathComponent("gemini-cli")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli-core")
                .appendingPathComponent("dist")
                .appendingPathComponent("src")
                .appendingPathComponent("code_assist")
                .appendingPathComponent("oauth2.js")

            if includeOAuth {
                try FileManager.default.createDirectory(
                    at: oauthPath.deletingLastPathComponent(),
                    withIntermediateDirectories: true)

                let oauthContent = """
                const OAUTH_CLIENT_ID = 'test-client-id';
                const OAUTH_CLIENT_SECRET = 'test-client-secret';
                """
                try oauthContent.write(to: oauthPath, atomically: true, encoding: .utf8)
            }

            let geminiBinary = binDir.appendingPathComponent("gemini")
            try "#!/bin/bash\nexit 0\n".write(to: geminiBinary, atomically: true, encoding: .utf8)
            try FileManager.default.setAttributes(
                [.posixPermissions: 0o755],
                ofItemAtPath: geminiBinary.path)
            return geminiBinary

        case .fnmBundle:
            // Mirror a real fnm multishell layout: bin/gemini is a single relative
            // symlink into the same multishell's lib/node_modules/@google/gemini-cli,
            // which is a plain directory with the real package.json + bundle/*.js.
            let multishellRoot = self.homeURL
                .appendingPathComponent("Library")
                .appendingPathComponent("Caches")
                .appendingPathComponent("fnm_multishells")
                .appendingPathComponent("12345_67890")
            let binDir = multishellRoot.appendingPathComponent("bin")
            let packageRoot = multishellRoot
                .appendingPathComponent("lib")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli")
            let bundleDir = packageRoot.appendingPathComponent("bundle")
            try FileManager.default.createDirectory(at: binDir, withIntermediateDirectories: true)
            try FileManager.default.createDirectory(at: bundleDir, withIntermediateDirectories: true)

            let packageJSON = """
            {
              "name": "@google/gemini-cli"
            }
            """
            try packageJSON.write(
                to: packageRoot.appendingPathComponent("package.json"),
                atomically: true,
                encoding: .utf8)

            let chunkName = "chunk-TEST123.js"
            let geminiEntry = bundleDir.appendingPathComponent("gemini.js")
            let geminiContent = """
            #!/usr/bin/env node
            import { start } from "./\(chunkName)";
            start();
            """
            try geminiContent.write(to: geminiEntry, atomically: true, encoding: .utf8)
            try FileManager.default.setAttributes(
                [.posixPermissions: 0o755],
                ofItemAtPath: geminiEntry.path)

            let chunkContent = if includeOAuth {
                """
                export const start = () => {};
                const OAUTH_CLIENT_ID = 'test-client-id';
                const OAUTH_CLIENT_SECRET = 'test-client-secret';
                """
            } else {
                "export const start = () => {};\n"
            }
            try chunkContent.write(
                to: bundleDir.appendingPathComponent(chunkName),
                atomically: true,
                encoding: .utf8)

            // Relative symlink matching what fnm actually creates:
            //   fnm_multishells/XXX/bin/gemini -> ../lib/node_modules/@google/gemini-cli/bundle/gemini.js
            // Use the path-based API so the target is stored as a literal relative
            // string; the URL-based API resolves URL(fileURLWithPath: "../...") against
            // the process CWD, which produces a bogus absolute target.
            let geminiBinary = binDir.appendingPathComponent("gemini")
            try FileManager.default.createSymbolicLink(
                atPath: geminiBinary.path,
                withDestinationPath: "../lib/node_modules/@google/gemini-cli/bundle/gemini.js")

            return geminiBinary

        case .homebrewBundle:
            return try self.writeFakeHomebrewGeminiCLI(base: base, includeOAuth: includeOAuth)
        }
    }

    private func writeFakeHomebrewGeminiCLI(base: URL, includeOAuth: Bool) throws -> URL {
        let cellarRoot = base
            .appendingPathComponent("Cellar")
            .appendingPathComponent("gemini-cli")
            .appendingPathComponent("0.41.2")
        let binDir = cellarRoot.appendingPathComponent("bin")
        let packageRoot = cellarRoot
            .appendingPathComponent("libexec")
            .appendingPathComponent("lib")
            .appendingPathComponent("node_modules")
            .appendingPathComponent("@google")
            .appendingPathComponent("gemini-cli")
        let bundleDir = packageRoot.appendingPathComponent("bundle")
        try FileManager.default.createDirectory(at: binDir, withIntermediateDirectories: true)
        try FileManager.default.createDirectory(at: bundleDir, withIntermediateDirectories: true)

        let packageJSON = """
        {
          "name": "@google/gemini-cli"
        }
        """
        try packageJSON.write(
            to: packageRoot.appendingPathComponent("package.json"),
            atomically: true,
            encoding: .utf8)

        let entry = bundleDir.appendingPathComponent("gemini.js")
        try "#!/usr/bin/env node\nawait import('./gemini-HASH.js');\n".write(
            to: entry,
            atomically: true,
            encoding: .utf8)
        try FileManager.default.setAttributes(
            [.posixPermissions: 0o755],
            ofItemAtPath: entry.path)
        try "export const run = () => {};\n".write(
            to: bundleDir.appendingPathComponent("gemini-HASH.js"),
            atomically: true,
            encoding: .utf8)

        let chunkContent = if includeOAuth {
            """
            var OAUTH_CLIENT_ID = "test-client-id";
            var OAUTH_CLIENT_SECRET = "test-client-secret";
            """
        } else {
            "export const unrelated = true;\n"
        }
        try chunkContent.write(
            to: bundleDir.appendingPathComponent("chunk-OAUTH.js"),
            atomically: true,
            encoding: .utf8)

        let geminiBinary = binDir.appendingPathComponent("gemini")
        try FileManager.default.createSymbolicLink(
            atPath: geminiBinary.path,
            withDestinationPath: "../libexec/lib/node_modules/@google/gemini-cli/bundle/gemini.js")
        return geminiBinary
    }

    func writeFakeFnm(
        currentVersion: String = "v24.6.0",
        npmRoot: String? = nil,
        geminiPackageJSONPath: String,
        holdNpmRootStdoutOpen: Bool = false) throws -> URL
    {
        let binDir = self.homeURL.appendingPathComponent("bin")
        try FileManager.default.createDirectory(at: binDir, withIntermediateDirectories: true)

        let fnmPath = binDir.appendingPathComponent("fnm")
        let stdoutHolder = holdNpmRootStdoutOpen
            ? #"""
            python3 - <<'PY'
            import os
            import subprocess
            import sys

            child = subprocess.Popen(
                [sys.executable, "-c", "import time; time.sleep(30)"],
                stdin=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True,
            )
            with open(os.environ["CODEXBAR_TEST_CHILD_PID_FILE"], "w") as handle:
                handle.write(str(child.pid))
            PY
            """#
            : ":"
        let script = if let npmRoot {
            """
            #!/bin/bash
            if [ "$1" = "current" ]; then
              printf '%s\n' "\(currentVersion)"
              exit 0
            fi

            if [ "$1" = "exec" ] && [ "$4" = "npm" ] && [ "$5" = "root" ] && [ "$6" = "-g" ]; then
              \(stdoutHolder)
              printf '%s\n' "\(npmRoot)"
              exit 0
            fi

            if [ "$1" = "exec" ] && [ "$4" = "node" ]; then
              printf '%s\n' "\(geminiPackageJSONPath)"
              exit 0
            fi

            exit 1
            """
        } else {
            """
            #!/bin/bash
            if [ "$1" = "current" ]; then
              printf '%s\n' "\(currentVersion)"
              exit 0
            fi

            if [ "$1" = "exec" ]; then
              printf '%s\n' "\(geminiPackageJSONPath)"
              exit 0
            fi

            exit 1
            """
        }
        try script.write(to: fnmPath, atomically: true, encoding: .utf8)
        try FileManager.default.setAttributes(
            [.posixPermissions: 0o755],
            ofItemAtPath: fnmPath.path)
        return fnmPath
    }
}
