import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("~/database/context", () => ({
  database: vi.fn(),
}));

import { database } from "~/database/context";
import { getGolfSkillsForSeason, getGolfSkillsMap, batchUpsertGolfSkills } from "../golf-skills";

const mockDb = {
  select: vi.fn().mockReturnThis(),
  from: vi.fn().mockReturnThis(),
  where: vi.fn().mockReturnThis(),
  innerJoin: vi.fn().mockReturnThis(),
  leftJoin: vi.fn().mockReturnThis(),
  orderBy: vi.fn().mockReturnThis(),
  insert: vi.fn().mockReturnThis(),
  values: vi.fn().mockReturnThis(),
  onConflictDoUpdate: vi.fn().mockReturnThis(),
};

beforeEach(() => {
  vi.clearAllMocks();
  (database as ReturnType<typeof vi.fn>).mockReturnValue(mockDb);

  mockDb.select.mockReturnValue(mockDb);
  mockDb.from.mockReturnValue(mockDb);
  mockDb.where.mockReturnValue(mockDb);
  mockDb.innerJoin.mockReturnValue(mockDb);
  mockDb.leftJoin.mockReturnValue(mockDb);
  mockDb.orderBy.mockReturnValue(mockDb);
  mockDb.insert.mockReturnValue(mockDb);
  mockDb.values.mockReturnValue(mockDb);
  mockDb.onConflictDoUpdate.mockResolvedValue(undefined);
});

describe("getGolfSkillsMap", () => {
  it("returns an empty Map when no records exist", async () => {
    mockDb.where.mockResolvedValue([]);
    const result = await getGolfSkillsMap("season-1");
    expect(result).toBeInstanceOf(Map);
    expect(result.size).toBe(0);
  });

  it("returns a Map keyed by seasonParticipantId with sgTotal converted to number", async () => {
    mockDb.where.mockResolvedValue([
      {
        seasonParticipantId: "sp-1",
        sportsSeasonId: "season-1",
        id: "skill-1",
        sgTotal: "2.50",
        datagolfRank: 3,
        mastersOdds: 800,
        usOpenOdds: null,
        openChampionshipOdds: null,
        pgaChampionshipOdds: null,
        updatedAt: new Date("2026-01-01"),
      },
    ]);
    const result = await getGolfSkillsMap("season-1");
    expect(result.size).toBe(1);
    const record = result.get("sp-1");
    expect(record).toBeDefined();
    expect(record?.sgTotal).toBe(2.5);
    expect(record?.participantId).toBe("sp-1");
    expect(record?.mastersOdds).toBe(800);
  });

  it("preserves null sgTotal", async () => {
    mockDb.where.mockResolvedValue([
      {
        seasonParticipantId: "sp-1",
        sportsSeasonId: "season-1",
        id: "skill-1",
        sgTotal: null,
        datagolfRank: null,
        mastersOdds: null,
        usOpenOdds: null,
        openChampionshipOdds: null,
        pgaChampionshipOdds: null,
        updatedAt: new Date("2026-01-01"),
      },
    ]);
    const result = await getGolfSkillsMap("season-1");
    expect(result.get("sp-1")?.sgTotal).toBeNull();
  });
});

describe("getGolfSkillsForSeason", () => {
  it("returns records joined with participant names and converts sgTotal", async () => {
    mockDb.orderBy.mockResolvedValue([
      {
        id: "skill-1",
        participantId: "sp-1",
        sportsSeasonId: "season-1",
        sgTotal: "1.24",
        datagolfRank: 10,
        mastersOdds: 400,
        usOpenOdds: null,
        openChampionshipOdds: null,
        pgaChampionshipOdds: null,
        updatedAt: new Date("2026-01-01"),
        participantName: "Rory McIlroy",
      },
    ]);
    const result = await getGolfSkillsForSeason("season-1");
    expect(result).toHaveLength(1);
    expect(result[0].participantName).toBe("Rory McIlroy");
    expect(result[0].sgTotal).toBe(1.24);
    expect(result[0].mastersOdds).toBe(400);
  });

  it("returns empty array when no records exist", async () => {
    mockDb.orderBy.mockResolvedValue([]);
    const result = await getGolfSkillsForSeason("season-1");
    expect(result).toEqual([]);
  });
});

describe("batchUpsertGolfSkills", () => {
  it("does nothing when given an empty array", async () => {
    await batchUpsertGolfSkills([]);
    expect(mockDb.insert).not.toHaveBeenCalled();
  });

  it("writes canonical rows using resolved canonical participant ids", async () => {
    mockDb.where.mockResolvedValueOnce([
      { id: "sp-1", canonicalId: "canon-1" },
      { id: "sp-2", canonicalId: "canon-2" },
    ]);

    await batchUpsertGolfSkills([
      { participantId: "sp-1", sgTotal: 2.5, mastersOdds: 400 },
      { participantId: "sp-2", sgTotal: null, usOpenOdds: 800 },
    ]);

    expect(mockDb.insert).toHaveBeenCalledOnce();
    expect(mockDb.values).toHaveBeenCalledOnce();
    expect(mockDb.onConflictDoUpdate).toHaveBeenCalledOnce();

    const passedValues = mockDb.values.mock.calls[0][0] as Array<{
      participantId: string;
      sgTotal: string | null;
      mastersOdds: number | null;
      usOpenOdds: number | null;
    }>;
    expect(passedValues).toHaveLength(2);
    expect(passedValues[0].participantId).toBe("canon-1");
    expect(passedValues[0].sgTotal).toBe("2.5");
    expect(passedValues[0].mastersOdds).toBe(400);
    expect(passedValues[1].participantId).toBe("canon-2");
    expect(passedValues[1].sgTotal).toBeNull();
    expect(passedValues[1].usOpenOdds).toBe(800);
  });

  it("skips season_participants that have no canonical link", async () => {
    mockDb.where.mockResolvedValueOnce([]);

    await batchUpsertGolfSkills([
      { participantId: "sp-1", sgTotal: 2.5 },
    ]);

    expect(mockDb.insert).not.toHaveBeenCalled();
  });
});
