import { buildContainerEnv, rewriteLocalhostForDocker } from "./containerEnv.js";
// Mock dockerode before importing TestExecutionService
const mockRun = jest.fn();
const mockListImages = jest.fn();
jest.mock("dockerode", () => {
    return jest.fn().mockImplementation(() => ({
        run: mockRun,
        listImages: mockListImages,
    }));
});
// Mock fs for workspace/file validation
jest.mock("fs", () => ({
    ...jest.requireActual("fs"),
    accessSync: jest.fn(),
    existsSync: jest.fn().mockReturnValue(true),
    readdirSync: jest.fn().mockImplementation((_path, options) => {
        if (options?.withFileTypes) {
            return [{ name: "test_file.py", isFile: () => true, isDirectory: () => false }];
        }
        return ["test_file.py"];
    }),
    readFileSync: jest.fn().mockReturnValue(""),
}));
// Mock logger
jest.mock("../utils/logger.js", () => ({
    logger: {
        debug: jest.fn(),
        info: jest.fn(),
        error: jest.fn(),
        warning: jest.fn(),
    },
}));
describe("rewriteLocalhostForDocker", () => {
    it("rewrites http://localhost:port", () => {
        expect(rewriteLocalhostForDocker("http://localhost:8000")).toBe("http://host.docker.internal:8000");
    });
    it("rewrites http://127.0.0.1:port", () => {
        expect(rewriteLocalhostForDocker("http://127.0.0.1:4200")).toBe("http://host.docker.internal:4200");
    });
    it("rewrites https://localhost:port", () => {
        expect(rewriteLocalhostForDocker("https://localhost:443")).toBe("https://host.docker.internal:443");
    });
    it("rewrites localhost without port", () => {
        expect(rewriteLocalhostForDocker("http://localhost/api")).toBe("http://host.docker.internal/api");
    });
    it("rewrites localhost with no path or port", () => {
        expect(rewriteLocalhostForDocker("http://localhost")).toBe("http://host.docker.internal");
    });
    it("does not rewrite non-localhost URLs", () => {
        expect(rewriteLocalhostForDocker("http://api.example.com:8000")).toBe("http://api.example.com:8000");
    });
    it("does not rewrite localhost in path", () => {
        expect(rewriteLocalhostForDocker("http://api.example.com/localhost")).toBe("http://api.example.com/localhost");
    });
    it("does not rewrite partial matches like localhost-extra", () => {
        expect(rewriteLocalhostForDocker("http://localhost-extra:8000")).toBe("http://localhost-extra:8000");
    });
});
describe("buildContainerEnv", () => {
    const baseOptions = { token: "test-token", language: "python" };
    const emptyHostEnv = {};
    it("always includes SKYRAMP_TEST_TOKEN and SKYRAMP_IN_DOCKER", () => {
        const env = buildContainerEnv(baseOptions, undefined, emptyHostEnv);
        expect(env).toContain("SKYRAMP_TEST_TOKEN=test-token");
        expect(env).toContain("SKYRAMP_IN_DOCKER=true");
    });
    it("defaults token to empty string when not provided", () => {
        const env = buildContainerEnv({ language: "typescript" }, undefined, emptyHostEnv);
        expect(env).toContain("SKYRAMP_TEST_TOKEN=");
    });
    it("adds PYTEST_ADDOPTS for python language", () => {
        const env = buildContainerEnv(baseOptions, undefined, emptyHostEnv);
        expect(env).toContain("PYTEST_ADDOPTS=--noconftest -c /dev/null");
    });
    it("does not add PYTEST_ADDOPTS for non-python language", () => {
        const env = buildContainerEnv({ ...baseOptions, language: "typescript" }, undefined, emptyHostEnv);
        expect(env.find((e) => e.startsWith("PYTEST_ADDOPTS="))).toBeUndefined();
    });
    it("adds PLAYWRIGHT_SAVE_STORAGE_PATH when saveStoragePath provided", () => {
        const env = buildContainerEnv(baseOptions, "/tmp/storage", emptyHostEnv);
        expect(env).toContain("PLAYWRIGHT_SAVE_STORAGE_PATH=/tmp/storage");
    });
    it("does not add PLAYWRIGHT_SAVE_STORAGE_PATH when not provided", () => {
        const env = buildContainerEnv(baseOptions, undefined, emptyHostEnv);
        expect(env.find((e) => e.startsWith("PLAYWRIGHT_SAVE_STORAGE_PATH="))).toBeUndefined();
    });
    describe("base URL env var forwarding", () => {
        it("rewrites localhost to host.docker.internal in bridge mode (default)", () => {
            const hostEnv = { SKYRAMP_TEST_BASE_URL: "http://localhost:8000" };
            const env = buildContainerEnv(baseOptions, undefined, hostEnv);
            expect(env).toContain("SKYRAMP_TEST_BASE_URL=http://host.docker.internal:8000");
        });
        it("rewrites 127.0.0.1 to host.docker.internal in bridge mode", () => {
            const hostEnv = { SKYRAMP_TEST_BASE_URL: "http://127.0.0.1:4200" };
            const env = buildContainerEnv(baseOptions, undefined, hostEnv);
            expect(env).toContain("SKYRAMP_TEST_BASE_URL=http://host.docker.internal:4200");
        });
        it("preserves localhost when useHostNetwork is true", () => {
            const hostEnv = { SKYRAMP_TEST_BASE_URL: "http://localhost:8000" };
            const env = buildContainerEnv({ ...baseOptions, useHostNetwork: true }, undefined, hostEnv);
            expect(env).toContain("SKYRAMP_TEST_BASE_URL=http://localhost:8000");
        });
        it("does not rewrite non-localhost URLs", () => {
            const hostEnv = { SKYRAMP_TEST_BASE_URL: "http://api.example.com:8000" };
            const env = buildContainerEnv(baseOptions, undefined, hostEnv);
            expect(env).toContain("SKYRAMP_TEST_BASE_URL=http://api.example.com:8000");
        });
        it("rewrites SKYRAMP_TEST_SERVICE_URL_* vars in bridge mode", () => {
            const hostEnv = {
                SKYRAMP_TEST_SERVICE_URL_BACKEND: "http://localhost:8000",
                SKYRAMP_TEST_SERVICE_URL_FRONTEND: "http://localhost:5173",
            };
            const env = buildContainerEnv(baseOptions, undefined, hostEnv);
            expect(env).toContain("SKYRAMP_TEST_SERVICE_URL_BACKEND=http://host.docker.internal:8000");
            expect(env).toContain("SKYRAMP_TEST_SERVICE_URL_FRONTEND=http://host.docker.internal:5173");
        });
        it("skips SKYRAMP_TEST_BASE_URL when value is empty", () => {
            const hostEnv = { SKYRAMP_TEST_BASE_URL: "" };
            const env = buildContainerEnv(baseOptions, undefined, hostEnv);
            expect(env.find((e) => e.startsWith("SKYRAMP_TEST_BASE_URL="))).toBeUndefined();
        });
        it("does not forward unrelated SKYRAMP_ vars", () => {
            const hostEnv = {
                SKYRAMP_TEST_BASE_URL: "http://localhost:8000",
                SKYRAMP_OTHER_VAR: "should-not-appear",
                SKYRAMP_TEST_SERVICE_NOT_URL: "also-no",
            };
            const env = buildContainerEnv(baseOptions, undefined, hostEnv);
            expect(env).toContain("SKYRAMP_TEST_BASE_URL=http://host.docker.internal:8000");
            expect(env.find((e) => e.includes("SKYRAMP_OTHER_VAR"))).toBeUndefined();
            expect(env.find((e) => e.includes("SKYRAMP_TEST_SERVICE_NOT_URL"))).toBeUndefined();
        });
    });
    it("forwards SKYRAMP_DEBUG from host env", () => {
        const hostEnv = { SKYRAMP_DEBUG: "true" };
        const env = buildContainerEnv(baseOptions, undefined, hostEnv);
        expect(env).toContain("SKYRAMP_DEBUG=true");
    });
    it("forwards API_KEY from host env", () => {
        const hostEnv = { API_KEY: "my-key" };
        const env = buildContainerEnv(baseOptions, undefined, hostEnv);
        expect(env).toContain("API_KEY=my-key");
    });
});
describe("detectSessionFiles", () => {
    // Import after mocks are set up so the fs mock applies
    let detectSessionFiles;
    let mockReadFileSync;
    beforeAll(async () => {
        const mod = await import("./TestExecutionService.js");
        detectSessionFiles = mod.detectSessionFiles;
        // eslint-disable-next-line @typescript-eslint/no-require-imports
        mockReadFileSync = require("fs").readFileSync;
    });
    it("detects string-literal storageState (TS/JS)", () => {
        mockReadFileSync.mockReturnValueOnce(`test.use({ storageState: '/abs/path/session.json' });`);
        expect(detectSessionFiles("/ws/test.spec.ts")).toEqual([
            "/abs/path/session.json",
        ]);
    });
    it("detects skyramp codegen path.join(__dirname, '<file>') pattern and resolves to host-absolute path", () => {
        // Reproduces SKYR-3321 generated test shape — must resolve to the host
        // absolute path so the executor's absolute-path mount branch makes the
        // file visible at that same path inside the container.
        mockReadFileSync.mockReturnValueOnce(`
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SESSION_STORAGE = path.join(__dirname, 'skyramp_session_storage.json');
test.use({ storageState: SESSION_STORAGE });
`);
        expect(detectSessionFiles("/Users/pedro/projects/cisco-xdr-tests/xdr_dashboard.spec.ts")).toEqual([
            "/Users/pedro/projects/cisco-xdr-tests/skyramp_session_storage.json",
        ]);
    });
    it("detects inline storageState: path.join(__dirname, '<file>')", () => {
        mockReadFileSync.mockReturnValueOnce(`test.use({ storageState: path.join(__dirname, 'session.json') });`);
        expect(detectSessionFiles("/ws/spec.ts")).toEqual(["/ws/session.json"]);
    });
    it("ignores storageState references inside comments", () => {
        mockReadFileSync.mockReturnValueOnce(`
// storageState: '/should/not/match.json'
// path.join(__dirname, 'also-not.json')
test('x', () => {});
`);
        expect(detectSessionFiles("/ws/spec.ts")).toEqual([]);
    });
});
describe("TestExecutionService.executeTest - Docker env forwarding", () => {
    // Import after mocks are set up
    let TestExecutionService;
    let EXECUTOR_DOCKER_IMAGE;
    beforeAll(async () => {
        const mod = await import("./TestExecutionService.js");
        TestExecutionService = mod.TestExecutionService;
        EXECUTOR_DOCKER_IMAGE = mod.EXECUTOR_DOCKER_IMAGE;
    });
    beforeEach(() => {
        jest.clearAllMocks();
        // Simulate image already cached
        mockListImages.mockResolvedValue([
            { RepoTags: [EXECUTOR_DOCKER_IMAGE] },
        ]);
    });
    afterEach(() => {
        // Clean up env vars we set during tests
        delete process.env.SKYRAMP_TEST_BASE_URL;
        delete process.env.SKYRAMP_TEST_SERVICE_URL_BACKEND;
        delete process.env.SKYRAMP_TEST_SERVICE_URL_FRONTEND;
    });
    it("passes SKYRAMP_TEST_BASE_URL to Docker container Env", async () => {
        process.env.SKYRAMP_TEST_BASE_URL = "http://external-host:8000";
        const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
        mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
        const service = new TestExecutionService();
        await service.executeTest({
            testFile: "/workspace/test_file.py",
            workspacePath: "/workspace",
            language: "python",
            testType: "smoke",
        });
        expect(mockRun).toHaveBeenCalledTimes(1);
        const dockerOptions = mockRun.mock.calls[0][3];
        expect(dockerOptions.Env).toContain("SKYRAMP_TEST_BASE_URL=http://external-host:8000");
    });
    it("passes multiple SKYRAMP_TEST_SERVICE_URL_* to Docker container Env", async () => {
        process.env.SKYRAMP_TEST_SERVICE_URL_BACKEND = "http://host1:8000";
        process.env.SKYRAMP_TEST_SERVICE_URL_FRONTEND = "http://host2:5173";
        const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
        mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
        const service = new TestExecutionService();
        await service.executeTest({
            testFile: "/workspace/test_file.py",
            workspacePath: "/workspace",
            language: "python",
            testType: "smoke",
        });
        const dockerOptions = mockRun.mock.calls[0][3];
        expect(dockerOptions.Env).toContain("SKYRAMP_TEST_SERVICE_URL_BACKEND=http://host1:8000");
        expect(dockerOptions.Env).toContain("SKYRAMP_TEST_SERVICE_URL_FRONTEND=http://host2:5173");
    });
    it("does not include base URL vars when not set in host env", async () => {
        const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
        mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
        const service = new TestExecutionService();
        await service.executeTest({
            testFile: "/workspace/test_file.py",
            workspacePath: "/workspace",
            language: "python",
            testType: "smoke",
        });
        const dockerOptions = mockRun.mock.calls[0][3];
        const envWithBaseUrl = dockerOptions.Env.filter((e) => e.startsWith("SKYRAMP_TEST_BASE_URL=") ||
            e.startsWith("SKYRAMP_TEST_SERVICE_URL_"));
        expect(envWithBaseUrl).toHaveLength(0);
    });
    // Approach B: every workspace mount is mirrored at the host-absolute path so
    // tests that embed absolute references (storageState, fixtures, snapshots)
    // resolve correctly inside the executor regardless of which path-shape the
    // codegen happens to emit.
    it("mirrors each workspace file mount at both /home/user/<f> and the host-absolute path", async () => {
        // eslint-disable-next-line @typescript-eslint/no-require-imports
        const fs = require("fs");
        fs.readdirSync.mockImplementation((_path, options) => {
            if (options?.withFileTypes) {
                return [
                    { name: "xdr_dashboard.spec.ts", isFile: () => true, isDirectory: () => false },
                    { name: "skyramp_session_storage.json", isFile: () => true, isDirectory: () => false },
                ];
            }
            return ["xdr_dashboard.spec.ts", "skyramp_session_storage.json"];
        });
        const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
        mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
        const service = new TestExecutionService();
        await service.executeTest({
            testFile: "/Users/pedro/projects/cisco-xdr-tests/xdr_dashboard.spec.ts",
            workspacePath: "/Users/pedro/projects/cisco-xdr-tests",
            language: "typescript",
            testType: "ui",
        });
        const dockerOptions = mockRun.mock.calls[0][3];
        const targets = dockerOptions.HostConfig.Mounts.map((m) => m.Target);
        // Canonical /home/user mount
        expect(targets).toContain("/home/user/xdr_dashboard.spec.ts");
        expect(targets).toContain("/home/user/skyramp_session_storage.json");
        // Host-absolute mirror — the fix for absolute paths leaking out of `__dirname`
        expect(targets).toContain("/Users/pedro/projects/cisco-xdr-tests/xdr_dashboard.spec.ts");
        expect(targets).toContain("/Users/pedro/projects/cisco-xdr-tests/skyramp_session_storage.json");
    });
    it("does not double-mount when workspacePath equals /home/user", async () => {
        // eslint-disable-next-line @typescript-eslint/no-require-imports
        const fs = require("fs");
        fs.readdirSync.mockImplementation((_path, options) => {
            if (options?.withFileTypes) {
                return [{ name: "test_file.py", isFile: () => true, isDirectory: () => false }];
            }
            return ["test_file.py"];
        });
        const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
        mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
        const service = new TestExecutionService();
        await service.executeTest({
            testFile: "/home/user/test_file.py",
            workspacePath: "/home/user",
            language: "python",
            testType: "smoke",
        });
        const dockerOptions = mockRun.mock.calls[0][3];
        const targetCounts = {};
        for (const m of dockerOptions.HostConfig.Mounts) {
            targetCounts[m.Target] = (targetCounts[m.Target] ?? 0) + 1;
        }
        // No mount target should appear twice (no host-absolute mirror when workspace == /home/user)
        for (const [t, n] of Object.entries(targetCounts)) {
            expect({ target: t, count: n }).toEqual({ target: t, count: 1 });
        }
    });
});
