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

// Mock the database context before importing any model
vi.mock("~/database/context", () => ({
  database: vi.fn(),
}));

import { database } from "~/database/context";
import { getSurfaceEloMap, batchUpsertSurfaceElos, getSurfaceElosForSeason } from "../surface-elo";

const mockDb = {
  select: vi.fn().mockReturnThis(),
  from: vi.fn().mockReturnThis(),
  where: vi.fn().mockReturnThis(),
  innerJoin: 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);

  // Reset chain to return mock itself (supports fluent chaining)
  mockDb.select.mockReturnValue(mockDb);
  mockDb.from.mockReturnValue(mockDb);
  mockDb.where.mockReturnValue(mockDb);
  mockDb.innerJoin.mockReturnValue(mockDb);
  mockDb.orderBy.mockReturnValue(mockDb);
  mockDb.insert.mockReturnValue(mockDb);
  mockDb.values.mockReturnValue(mockDb);
  mockDb.onConflictDoUpdate.mockResolvedValue(undefined);
});

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

  it("returns a Map keyed by participantId", async () => {
    mockDb.where.mockResolvedValue([
      { participantId: "p1", worldRanking: 1, eloHard: 1800, eloClay: 1700, eloGrass: 1600 },
      { participantId: "p2", worldRanking: null, eloHard: null, eloClay: 1750, eloGrass: null },
    ]);
    const result = await getSurfaceEloMap("season-1");
    expect(result.size).toBe(2);
    expect(result.get("p1")).toEqual({ worldRanking: 1, eloHard: 1800, eloClay: 1700, eloGrass: 1600 });
    expect(result.get("p2")).toEqual({ worldRanking: null, eloHard: null, eloClay: 1750, eloGrass: null });
  });

  it("preserves null surface Elos (does not coerce to 0)", async () => {
    mockDb.where.mockResolvedValue([
      { participantId: "p1", worldRanking: null, eloHard: null, eloClay: null, eloGrass: null },
    ]);
    const result = await getSurfaceEloMap("season-1");
    const entry = result.get("p1");
    expect(entry?.worldRanking).toBeNull();
    expect(entry?.eloHard).toBeNull();
    expect(entry?.eloClay).toBeNull();
    expect(entry?.eloGrass).toBeNull();
  });
});

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

  it("calls insert with the correct values", async () => {
    const inputs = [
      { participantId: "p1", sportsSeasonId: "s1", worldRanking: 1, eloHard: 1800, eloClay: 1700, eloGrass: 1600 },
      { participantId: "p2", sportsSeasonId: "s1", worldRanking: null, eloHard: null, eloClay: 1750, eloGrass: undefined },
    ];
    await batchUpsertSurfaceElos(inputs);
    expect(mockDb.insert).toHaveBeenCalledOnce();
    expect(mockDb.values).toHaveBeenCalledOnce();

    const passedValues = mockDb.values.mock.calls[0][0] as Array<{
      participantId: string;
      eloHard: number | null;
      eloClay: number | null;
      eloGrass: number | null;
    }>;
    expect(passedValues).toHaveLength(2);
    expect(passedValues[0].participantId).toBe("p1");
    expect(passedValues[0].eloHard).toBe(1800);
    expect(passedValues[1].eloHard).toBeNull();
    expect(passedValues[1].eloGrass).toBeNull(); // undefined → null
  });

  it("uses onConflictDoUpdate for upsert behaviour", async () => {
    await batchUpsertSurfaceElos([
      { participantId: "p1", sportsSeasonId: "s1", worldRanking: 5, eloHard: 2000 },
    ]);
    expect(mockDb.onConflictDoUpdate).toHaveBeenCalledOnce();
  });
});

describe("getSurfaceElosForSeason", () => {
  it("returns records joined with participant names", async () => {
    mockDb.orderBy.mockResolvedValue([
      {
        id: "rec-1",
        participantId: "p1",
        sportsSeasonId: "s1",
        worldRanking: 1,
        eloHard: 1800,
        eloClay: 1700,
        eloGrass: 1600,
        updatedAt: new Date("2025-01-01"),
        participantName: "Carlos Alcaraz",
      },
    ]);
    const result = await getSurfaceElosForSeason("s1");
    expect(result).toHaveLength(1);
    expect(result[0].participantName).toBe("Carlos Alcaraz");
    expect(result[0].eloHard).toBe(1800);
  });

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