import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { clearProfileCache, getProfile } from "./profile";

// Mock the dependencies
vi.mock("../repositories/settings", () => ({
  getSetting: vi.fn(),
}));

vi.mock("./rxresume", () => ({
  getResume: vi.fn(),
  RxResumeAuthConfigError: class RxResumeAuthConfigError extends Error {
    constructor() {
      super("Reactive Resume credentials not configured.");
      this.name = "RxResumeAuthConfigError";
    }
  },
}));

import { getSetting } from "../repositories/settings";
import { getResume, RxResumeAuthConfigError } from "./rxresume";

function mockRxResumeOnlyFlow(resumeId: string) {
  vi.mocked(getSetting).mockImplementation(async (key: string) => {
    if (key === "localResumeProfilePath") return null;
    if (key === "rxresumeMode") return "v5";
    if (
      key === "rxresumeBaseResumeId" ||
      key === "rxresumeBaseResumeIdV5" ||
      key === "rxresumeBaseResumeIdV4"
    ) {
      return resumeId;
    }
    return null;
  });
}

describe("getProfile", () => {
  beforeEach(() => {
    vi.resetAllMocks();
    clearProfileCache();
    delete process.env.JOBOPS_LOCAL_RESUME_PATH;
  });

  it("should throw an error if no local file and rxresumeBaseResumeId is not configured", async () => {
    vi.mocked(getSetting).mockImplementation(async (key: string) => {
      if (key === "localResumeProfilePath") return null;
      return null;
    });

    await expect(getProfile()).rejects.toThrow(
      /Base resume not configured|JOBOPS_LOCAL_RESUME_PATH|local resume path/i,
    );
  });

  it("should fetch profile from Reactive Resume when configured", async () => {
    const mockResumeData = { basics: { name: "Test User" } };
    mockRxResumeOnlyFlow("test-resume-id");
    vi.mocked(getResume).mockResolvedValue({
      id: "test-resume-id",
      data: mockResumeData,
    } as any);

    const profile = await getProfile();

    expect(getResume).toHaveBeenCalledWith("test-resume-id");
    expect(profile).toEqual(mockResumeData);
  });

  it("should cache the profile and not refetch on subsequent calls", async () => {
    const mockResumeData = { basics: { name: "Test User" } };
    mockRxResumeOnlyFlow("test-resume-id");
    vi.mocked(getResume).mockResolvedValue({
      id: "test-resume-id",
      data: mockResumeData,
    } as any);

    await getProfile();
    await getProfile();

    expect(getResume).toHaveBeenCalledTimes(1);
  });

  it("should refetch when forceRefresh is true", async () => {
    const mockResumeData = { basics: { name: "Test User" } };
    mockRxResumeOnlyFlow("test-resume-id");
    vi.mocked(getResume).mockResolvedValue({
      id: "test-resume-id",
      data: mockResumeData,
    } as any);

    await getProfile();
    await getProfile(true);

    expect(getResume).toHaveBeenCalledTimes(2);
    expect(vi.mocked(getResume).mock.calls[0]).toEqual(["test-resume-id"]);
    expect(vi.mocked(getResume).mock.calls[1]).toEqual([
      "test-resume-id",
      { forceRefresh: true },
    ]);
  });

  it("should throw user-friendly error on credential issues", async () => {
    mockRxResumeOnlyFlow("test-resume-id");
    vi.mocked(getResume).mockRejectedValue(
      new (RxResumeAuthConfigError as unknown as new () => Error)(),
    );

    await expect(getProfile()).rejects.toThrow(
      "Reactive Resume credentials not configured.",
    );
  });

  it("should throw error if resume data is empty", async () => {
    mockRxResumeOnlyFlow("test-resume-id");
    vi.mocked(getResume).mockResolvedValue({
      id: "test-resume-id",
      data: null,
    } as any);

    await expect(getProfile()).rejects.toThrow(
      "Resume data is empty or invalid",
    );
  });

  it("loads profile from localResumeProfilePath when set", async () => {
    const dir = await mkdtemp(join(tmpdir(), "jobber-profile-"));
    const filePath = join(dir, "resume.json");
    const mockResumeData = { basics: { name: "Local User" } };
    await writeFile(filePath, JSON.stringify(mockResumeData), "utf8");

    vi.mocked(getSetting).mockImplementation(async (key: string) => {
      if (key === "localResumeProfilePath") return filePath;
      return null;
    });

    const profile = await getProfile();
    expect(profile).toEqual(mockResumeData);
    expect(getResume).not.toHaveBeenCalled();
  });

  it("prefers JOBOPS_LOCAL_RESUME_PATH over RxResume", async () => {
    const dir = await mkdtemp(join(tmpdir(), "jobber-profile-env-"));
    const filePath = join(dir, "resume.json");
    await writeFile(
      filePath,
      JSON.stringify({ basics: { name: "Env User" } }),
      "utf8",
    );
    process.env.JOBOPS_LOCAL_RESUME_PATH = filePath;

    mockRxResumeOnlyFlow("ignored-id");
    vi.mocked(getResume).mockResolvedValue({
      id: "ignored-id",
      data: { basics: { name: "Remote User" } },
    } as any);

    const profile = await getProfile();
    expect(profile.basics?.name).toBe("Env User");
    expect(getResume).not.toHaveBeenCalled();
  });
});
