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";

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

import { getSetting } from "../repositories/settings";

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

  it("throws when no local resume path is configured", async () => {
    vi.mocked(getSetting).mockResolvedValue(null);

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

  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);
  });

  it("caches the profile and does not re-read on subsequent calls", async () => {
    const dir = await mkdtemp(join(tmpdir(), "jobber-profile-cache-"));
    const filePath = join(dir, "resume.json");
    await writeFile(
      filePath,
      JSON.stringify({ basics: { name: "Cached User" } }),
      "utf8",
    );

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

    const first = await getProfile();
    const second = await getProfile();

    expect(first).toBe(second);
    expect(second.basics?.name).toBe("Cached User");
  });

  it("refetches from disk when forceRefresh is true", async () => {
    const dir = await mkdtemp(join(tmpdir(), "jobber-profile-refresh-"));
    const filePath = join(dir, "resume.json");
    await writeFile(
      filePath,
      JSON.stringify({ basics: { name: "First" } }),
      "utf8",
    );

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

    await getProfile();
    await writeFile(
      filePath,
      JSON.stringify({ basics: { name: "Second" } }),
      "utf8",
    );
    const profile = await getProfile(true);

    expect(profile.basics?.name).toBe("Second");
  });

  it("prefers JOBOPS_LOCAL_RESUME_PATH over the settings path", 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;

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

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

  it("throws when the local file is not valid JSON", async () => {
    const dir = await mkdtemp(join(tmpdir(), "jobber-profile-bad-"));
    const filePath = join(dir, "resume.json");
    await writeFile(filePath, "not json", "utf8");

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

    await expect(getProfile()).rejects.toThrow(/not valid JSON/i);
  });
});
