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

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

import {
  createCanonicalParticipant,
  getCanonicalParticipantById,
  findCanonicalParticipantsBySport,
  findCanonicalParticipantBySportName,
  upsertCanonicalParticipantBySportName,
} from "../participant";
import { database } from "~/database/context";

const SPORT_ID = "sport-1";
const PARTICIPANT_ID = "participant-1";

const SAMPLE_PARTICIPANT = {
  id: PARTICIPANT_ID,
  sportId: SPORT_ID,
  name: "Novak Djokovic",
  externalKey: "djokovic-n",
  metadata: { atp_id: "12345" },
  createdAt: new Date("2026-01-01T00:00:00Z"),
  updatedAt: new Date("2026-01-01T00:00:00Z"),
};

function makeInsertDb(returnValue: object) {
  return {
    insert: vi.fn().mockReturnValue({
      values: vi.fn().mockReturnValue({
        returning: vi.fn().mockResolvedValue([returnValue]),
      }),
    }),
  };
}

function makeSelectDb(rows: object[]) {
  return {
    select: vi.fn().mockReturnValue({
      from: vi.fn().mockReturnValue({
        where: vi.fn().mockReturnValue({
          limit: vi.fn().mockResolvedValue(rows),
          orderBy: vi.fn().mockResolvedValue(rows),
        }),
      }),
    }),
  };
}

beforeEach(() => {
  vi.clearAllMocks();
});

describe("createCanonicalParticipant", () => {
  it("inserts a participant and returns it", async () => {
    vi.mocked(database).mockReturnValue(makeInsertDb(SAMPLE_PARTICIPANT) as never);

    const result = await createCanonicalParticipant({
      sportId: SPORT_ID,
      name: "Novak Djokovic",
    });

    expect(result).toEqual(SAMPLE_PARTICIPANT);
  });
});

describe("getCanonicalParticipantById", () => {
  it("returns the participant when found", async () => {
    vi.mocked(database).mockReturnValue(makeSelectDb([SAMPLE_PARTICIPANT]) as never);

    const result = await getCanonicalParticipantById(PARTICIPANT_ID);

    expect(result).toEqual(SAMPLE_PARTICIPANT);
  });

  it("returns null when not found", async () => {
    vi.mocked(database).mockReturnValue(makeSelectDb([]) as never);

    const result = await getCanonicalParticipantById("nonexistent");

    expect(result).toBeNull();
  });
});

describe("findCanonicalParticipantsBySport", () => {
  it("returns participants ordered by name", async () => {
    const participants = [SAMPLE_PARTICIPANT];
    vi.mocked(database).mockReturnValue(makeSelectDb(participants) as never);

    const result = await findCanonicalParticipantsBySport(SPORT_ID);

    expect(result).toEqual(participants);
  });
});

describe("findCanonicalParticipantBySportName", () => {
  it("returns the participant when found", async () => {
    vi.mocked(database).mockReturnValue(makeSelectDb([SAMPLE_PARTICIPANT]) as never);

    const result = await findCanonicalParticipantBySportName(SPORT_ID, "Novak Djokovic");

    expect(result).toEqual(SAMPLE_PARTICIPANT);
  });

  it("returns null when not found", async () => {
    vi.mocked(database).mockReturnValue(makeSelectDb([]) as never);

    const result = await findCanonicalParticipantBySportName(SPORT_ID, "Roger Federer");

    expect(result).toBeNull();
  });
});

describe("upsertCanonicalParticipantBySportName", () => {
  it("returns existing participant if found", async () => {
    vi.mocked(database).mockReturnValue(makeSelectDb([SAMPLE_PARTICIPANT]) as never);

    const result = await upsertCanonicalParticipantBySportName(SPORT_ID, "Novak Djokovic");

    expect(result).toEqual(SAMPLE_PARTICIPANT);
  });

  it("creates new participant if not found", async () => {
    let callCount = 0;
    vi.mocked(database).mockImplementation(() => {
      callCount++;
      return (callCount === 1 ? makeSelectDb([]) : makeInsertDb(SAMPLE_PARTICIPANT)) as never;
    });

    const result = await upsertCanonicalParticipantBySportName(SPORT_ID, "Novak Djokovic");

    expect(result).toEqual(SAMPLE_PARTICIPANT);
  });

  it("passes extra fields when creating", async () => {
    let callCount = 0;
    vi.mocked(database).mockImplementation(() => {
      callCount++;
      return (callCount === 1 ? makeSelectDb([]) : makeInsertDb(SAMPLE_PARTICIPANT)) as never;
    });

    const result = await upsertCanonicalParticipantBySportName(
      SPORT_ID,
      "Novak Djokovic",
      { externalKey: "djokovic-n" }
    );

    expect(result).toEqual(SAMPLE_PARTICIPANT);
  });
});
