import { readFile } from "node:fs/promises";
import { input, select } from "@inquirer/prompts";
import nock from "nock";
import { fetchProject } from "../../common/middleware/get-project-param.js";
import { printResultToConsoleAndExitGracefully } from "../../common/output.js";
import { cleanupTest, setupAuthenticatedNock, setupTestEnvironment, TEST_ACCOUNT_NAME, TEST_API_BASE, TEST_AUTH_TOKEN, TEST_PROJECT_NAME, } from "./test-utils.js";
jest.mock("node:fs/promises", () => ({
    readFile: jest.fn().mockRejectedValue(new Error("no package.json")),
}));
jest.mock("../../common/terminal.js", () => ({
    isInteractiveTerminal: jest.fn().mockReturnValue(true),
}));
jest.mock("../../common/read-linked-config.js", () => ({
    __esModule: true,
    confirmLinkedValue: jest.fn().mockResolvedValue(false),
    readLinkedConfig: jest.fn().mockResolvedValue({}),
    USE_LINKED_DETAILS: "useLinkedDetails",
    ignoreLinkedDetails: jest
        .fn()
        .mockImplementation((argv) => {
        argv["useLinkedDetails"] = false;
    }),
}));
const NEW_PROJECT_NAME = "my-new-project";
const PACKAGE_JSON_NAME = "my-package-from-json";
const baseArgv = () => ({
    authToken: TEST_AUTH_TOKEN,
    account: TEST_ACCOUNT_NAME,
    useLinkedDetails: false,
});
describe("fetchProject middleware", () => {
    beforeEach(() => {
        setupTestEnvironment();
        nock.disableNetConnect();
        jest.clearAllMocks();
        jest
            .mocked(readFile)
            .mockRejectedValue(new Error("ENOENT: no package.json"));
    });
    afterEach(() => {
        cleanupTest();
    });
    describe("when projects exist and user selects an existing project", () => {
        it("sets argv.project to the selected project name", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            const argv = baseArgv();
            await fetchProject(argv);
            expect(argv.project).toBe(TEST_PROJECT_NAME);
            expect(select).toHaveBeenCalledTimes(1);
        });
    });
    describe("when projects exist and user selects '+ Create a new project'", () => {
        it("prompts for a name, calls the create API, and sets argv.project", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            const createScope = setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .reply(200, { name: NEW_PROJECT_NAME });
            jest.mocked(select).mockResolvedValueOnce(null);
            jest.mocked(input).mockResolvedValueOnce(NEW_PROJECT_NAME);
            const argv = baseArgv();
            await fetchProject(argv);
            expect(argv.project).toBe(NEW_PROJECT_NAME);
            expect(select).toHaveBeenCalledTimes(1);
            expect(input).toHaveBeenCalledTimes(1);
            expect(input).toHaveBeenCalledWith(expect.objectContaining({
                message: `Enter a name for the new project in account '${TEST_ACCOUNT_NAME}'`,
            }));
            expect(createScope.isDone()).toBe(true);
        });
        it("uses package.json name as the default input value when available", async () => {
            jest
                .mocked(readFile)
                .mockResolvedValueOnce(JSON.stringify({ name: PACKAGE_JSON_NAME }));
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .reply(200, { name: NEW_PROJECT_NAME });
            jest.mocked(select).mockResolvedValueOnce(null);
            jest.mocked(input).mockResolvedValueOnce(NEW_PROJECT_NAME);
            const argv = baseArgv();
            await fetchProject(argv);
            expect(input).toHaveBeenCalledWith(expect.objectContaining({
                message: `Enter a name for the new project in account '${TEST_ACCOUNT_NAME}'`,
                default: PACKAGE_JSON_NAME,
            }));
        });
        it("calls process.exit(1) when user aborts the project name prompt", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            jest.mocked(select).mockResolvedValueOnce(null);
            const exitError = Object.assign(new Error("User aborted"), {
                name: "ExitPromptError",
            });
            jest.mocked(input).mockRejectedValueOnce(exitError);
            const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
                throw new Error("process.exit(1) called");
            }));
            try {
                await expect(fetchProject(baseArgv())).rejects.toThrow("process.exit(1) called");
                expect(exitSpy).toHaveBeenCalledWith(1);
            }
            finally {
                exitSpy.mockRestore();
            }
        });
        it("propagates unexpected errors thrown by the input prompt", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            jest.mocked(select).mockResolvedValueOnce(null);
            jest.mocked(input).mockRejectedValueOnce(new Error("keyboard exploded"));
            await expect(fetchProject(baseArgv())).rejects.toThrow("keyboard exploded");
        });
        it("exits with code 1 on non-retryable HTTP errors from the create API without retrying", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .reply(401, { title: "Unauthorized" });
            jest.mocked(select).mockResolvedValueOnce(null);
            jest.mocked(input).mockResolvedValueOnce(NEW_PROJECT_NAME);
            const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
                throw new Error("process.exit(1) called");
            }));
            try {
                await expect(fetchProject(baseArgv())).rejects.toThrow("process.exit(1) called");
                expect(exitSpy).toHaveBeenCalledWith(1);
                expect(input).toHaveBeenCalledTimes(1);
            }
            finally {
                exitSpy.mockRestore();
            }
        });
        it("exits with code 1 on network errors from the create API without retrying", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .replyWithError("connect ECONNREFUSED");
            jest.mocked(select).mockResolvedValueOnce(null);
            jest.mocked(input).mockResolvedValueOnce(NEW_PROJECT_NAME);
            const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
                throw new Error("process.exit(1) called");
            }));
            try {
                await expect(fetchProject(baseArgv())).rejects.toThrow("process.exit(1) called");
                expect(exitSpy).toHaveBeenCalledWith(1);
                expect(input).toHaveBeenCalledTimes(1);
            }
            finally {
                exitSpy.mockRestore();
            }
        });
        it("re-prompts after a failed create API call and succeeds on retry", async () => {
            const RETRY_PROJECT_NAME = "my-new-project-retry";
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .reply(422, { detail: "Project name already taken" });
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: RETRY_PROJECT_NAME })
                .reply(200, { name: RETRY_PROJECT_NAME });
            jest.mocked(select).mockResolvedValueOnce(null);
            jest
                .mocked(input)
                .mockResolvedValueOnce(NEW_PROJECT_NAME)
                .mockResolvedValueOnce(RETRY_PROJECT_NAME);
            const argv = baseArgv();
            await fetchProject(argv);
            expect(argv.project).toBe(RETRY_PROJECT_NAME);
            expect(input).toHaveBeenCalledTimes(2);
            expect(input).toHaveBeenNthCalledWith(2, expect.objectContaining({ default: NEW_PROJECT_NAME }));
            expect(jest.mocked(printResultToConsoleAndExitGracefully)).not.toHaveBeenCalled();
        });
    });
    describe("when user cancels the project picker (select prompt)", () => {
        it("calls process.exit(1) when select throws ExitPromptError", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            const exitError = Object.assign(new Error("User force-closed the prompt"), {
                name: "ExitPromptError",
            });
            jest.mocked(select).mockRejectedValueOnce(exitError);
            const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
                throw new Error("process.exit(1) called");
            }));
            try {
                await expect(fetchProject(baseArgv())).rejects.toThrow("process.exit(1) called");
                expect(exitSpy).toHaveBeenCalledWith(1);
            }
            finally {
                exitSpy.mockRestore();
            }
        });
        it("calls process.exit(1) when select throws isTtyError", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [{ name: TEST_PROJECT_NAME }] });
            const ttyError = Object.assign(new Error("Input is not a TTY"), {
                isTtyError: true,
            });
            jest.mocked(select).mockRejectedValueOnce(ttyError);
            const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
                throw new Error("process.exit(1) called");
            }));
            try {
                await expect(fetchProject(baseArgv())).rejects.toThrow("process.exit(1) called");
                expect(exitSpy).toHaveBeenCalledWith(1);
            }
            finally {
                exitSpy.mockRestore();
            }
        });
    });
    describe("when the projects list API fails", () => {
        it("exits critically on a 500 response", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(500, { error: "Internal Server Error" });
            await expect(fetchProject(baseArgv())).rejects.toThrow("Process would exit");
        });
        it("exits critically with a 404 response", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(404, {});
            await expect(fetchProject(baseArgv())).rejects.toThrow("Process would exit");
        });
        it("does not call select or input when the projects list request fails", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(500, {});
            try {
                await fetchProject(baseArgv());
            }
            catch {
            }
            expect(select).not.toHaveBeenCalled();
            expect(input).not.toHaveBeenCalled();
        });
    });
    describe("when user has no projects", () => {
        it("skips the picker, prompts directly for a name, and calls the create API", async () => {
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [] });
            const createScope = setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .reply(200, { name: NEW_PROJECT_NAME });
            jest.mocked(input).mockResolvedValueOnce(NEW_PROJECT_NAME);
            const argv = baseArgv();
            await fetchProject(argv);
            expect(argv.project).toBe(NEW_PROJECT_NAME);
            expect(select).not.toHaveBeenCalled();
            expect(input).toHaveBeenCalledTimes(1);
            expect(input).toHaveBeenCalledWith(expect.objectContaining({
                message: "You don't have any projects configured for this account. Please enter a project name to create one.",
            }));
            expect(createScope.isDone()).toBe(true);
        });
        it("uses package.json name as the default input value when available", async () => {
            jest
                .mocked(readFile)
                .mockResolvedValueOnce(JSON.stringify({ name: PACKAGE_JSON_NAME }));
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .get("/v1/projects")
                .query({ accountName: TEST_ACCOUNT_NAME })
                .reply(200, { data: [] });
            setupAuthenticatedNock(nock(TEST_API_BASE))
                .post(`/v1/projects?accountName=${encodeURIComponent(TEST_ACCOUNT_NAME)}`, { name: NEW_PROJECT_NAME })
                .reply(200, { name: NEW_PROJECT_NAME });
            jest.mocked(input).mockResolvedValueOnce(NEW_PROJECT_NAME);
            await fetchProject(baseArgv());
            expect(input).toHaveBeenCalledWith(expect.objectContaining({
                default: PACKAGE_JSON_NAME,
            }));
        });
    });
});
//# sourceMappingURL=get-project-param.integration.test.js.map