import { server } from "./../../../mocks/server";
import login, { resolveFiles, getToken } from "./login";
import { vi } from "vitest";
import { pathToFileURL } from "node:url";
import { existsSync } from "node:fs";
import { PathLike } from "node:fs";
import { mkdir, access, writeFile, readFile } from "fs/promises";
import { CREDENTIALS_DIR, CREDENTIALS_FILE } from "./credentials";
import { http, HttpResponse } from "msw";
import { AxiosError } from "axios";

vi.mock("./logger", () => ({
  initLogger: vi.fn(),
  logError: vi.fn(),
}));

vi.mock("fs/promises", () => ({
  readFile: vi.fn(),
  writeFile: vi.fn(),
  access: vi.fn(),
  mkdir: vi.fn(),
}));

const startMock = {
  succeed: vi.fn(),
  fail: vi.fn(),
};

vi.mock("ora", () => ({
  default: () => ({
    start: vi.fn().mockImplementation(() => startMock),
    info: vi.fn(),
  }),
}));

vi.mock("./rollbar.mjs", () => ({
  default: vi.fn(),
}));

vi.mock("open", () => ({
  default: vi.fn(),
}));

vi.mock("node:fs", () => ({
  existsSync: vi.fn(),
}));

vi.mock("node:url", () => ({
  pathToFileURL: vi.fn(),
}));

// Create a mock module factory
const mockConfigModule = {
  default: {
    authDomain: "test-domain.com",
  },
};

// Mock both possible config paths
vi.mock(`${process.cwd()}/embeddable.config.js`, () => mockConfigModule);
vi.mock(`${process.cwd()}/embeddable.config.ts`, () => mockConfigModule);

describe("login", () => {
  beforeEach(async () => {
    vi.resetModules();

    vi.mocked(readFile).mockImplementation(async () =>
      Buffer.from(`{"access_token":"mocked-token"}`),
    );

    vi.mocked(access).mockImplementation(async () => {
      throw new Error();
    });

    vi.mocked(mkdir).mockImplementation(async () => "mocked");

    vi.mocked(writeFile).mockImplementation(async () => undefined);

    // Mock that only the TS config exists
    vi.mocked(existsSync).mockImplementation((path: PathLike) =>
      path.toString().endsWith("embeddable.config.ts"),
    );

    // Mock URL creation
    vi.mocked(pathToFileURL).mockImplementation(
      (path) => new URL(`file://${path}`),
    );
  });

  vi.mock("./reportErrorToRollbar", () => vi.fn());
  vi.mock("./sleep", () => vi.fn());

  it("should resolve files", async () => {
    await resolveFiles();

    expect(access).toHaveBeenCalledWith(CREDENTIALS_DIR);
    expect(mkdir).toHaveBeenCalledWith(CREDENTIALS_DIR);
    expect(writeFile).toHaveBeenCalledWith(CREDENTIALS_FILE, "");
  });

  it("should get token", async () => {
    const token = await getToken();

    expect(token).toBe("mocked-token");
  });

  it("should login by saving token in the credentials file", async () => {
    await login();
    expect(startMock.succeed).toHaveBeenCalledWith(
      "You are successfully authenticated now!",
    );

    expect(writeFile).toHaveBeenCalledWith(
      CREDENTIALS_FILE,
      JSON.stringify({ access_token: "mocked-token " }),
    );
  });

  it("should fail to login when the response returns 500 error", async () => {
    vi.spyOn(console, "log").mockImplementation(() => undefined);
    vi.spyOn(process, "exit").mockImplementation(() => undefined as never);

    server.use(
      http.post("**/oauth/device/code", () => {
        return new HttpResponse(null, { status: 500 });
      }),
    );

    await login();

    expect(startMock.fail).toHaveBeenCalledWith(
      "Authentication failed. Please try again.",
    );

    expect((console.log as any).mock.calls[0][0]).toMatchInlineSnapshot(
      `[AxiosError: Request failed with status code 500]`,
    );
  });
});
