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

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

import {
  upsertRegularSeasonStandings,
  upsertManualStanding,
  getRegularSeasonStandings,
  getLastSyncedAt,
  deleteRegularSeasonStandings,
  getParticipantStanding,
} from "../regular-season-standings";
import { database } from "~/database/context";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeUpsertDb(returnedRows: object[] = []) {
  const returning = vi.fn().mockResolvedValue(returnedRows);
  const onConflictDoUpdate = vi.fn().mockReturnValue({ returning });
  const values = vi.fn().mockReturnValue({ onConflictDoUpdate });
  const insert = vi.fn().mockReturnValue({ values });
  const db: Record<string, unknown> = {
    insert,
    transaction: vi.fn().mockImplementation(async (fn: (tx: unknown) => unknown) => fn(db)),
  };
  return { db, insert, values, onConflictDoUpdate, returning };
}

function makeRecord(overrides: Record<string, unknown> = {}) {
  return {
    participantId: "p-1",
    sportsSeasonId: "ss-1",
    wins: 10,
    losses: 5,
    gamesPlayed: 15,
    ...overrides,
  };
}

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

// ---------------------------------------------------------------------------
// upsertRegularSeasonStandings
// ---------------------------------------------------------------------------

describe("upsertRegularSeasonStandings", () => {
  it("returns empty array immediately when given no records", async () => {
    const { db } = makeUpsertDb();
    vi.mocked(database).mockReturnValue(db as never);

    const result = await upsertRegularSeasonStandings([]);
    expect(result).toEqual([]);
    expect(db.insert).not.toHaveBeenCalled();
  });

  it("maps winPct and gamesBack to strings", async () => {
    const { db, values } = makeUpsertDb([]);
    vi.mocked(database).mockReturnValue(db as never);

    await upsertRegularSeasonStandings([
      makeRecord({ winPct: 0.667, gamesBack: 2.5 }),
    ]);

    const inserted = values.mock.calls[0][0];
    expect(inserted[0].winPct).toBe("0.667");
    expect(inserted[0].gamesBack).toBe("2.5");
  });

  it("maps null winPct and gamesBack to null", async () => {
    const { db, values } = makeUpsertDb([]);
    vi.mocked(database).mockReturnValue(db as never);

    await upsertRegularSeasonStandings([
      makeRecord({ winPct: null, gamesBack: null }),
    ]);

    const inserted = values.mock.calls[0][0];
    expect(inserted[0].winPct).toBeNull();
    expect(inserted[0].gamesBack).toBeNull();
  });

  it("uses the provided db instead of calling database()", async () => {
    const { db } = makeUpsertDb([]);

    await upsertRegularSeasonStandings([makeRecord()], db as never);

    expect(database).not.toHaveBeenCalled();
    expect(db.transaction).toHaveBeenCalled();
  });

  it("returns the rows from the DB", async () => {
    const row = { id: "row-1", wins: 10 };
    const { db } = makeUpsertDb([row]);
    vi.mocked(database).mockReturnValue(db as never);

    const result = await upsertRegularSeasonStandings([makeRecord()]);
    expect(result).toEqual([row]);
  });

  it("inserts multiple records in a single call", async () => {
    const { db, values } = makeUpsertDb([]);
    vi.mocked(database).mockReturnValue(db as never);

    await upsertRegularSeasonStandings([
      makeRecord({ participantId: "p-1" }),
      makeRecord({ participantId: "p-2" }),
    ]);

    expect(values.mock.calls[0][0]).toHaveLength(2);
  });

  it("sets syncedAt to now when not provided", async () => {
    const before = new Date();
    const { db, values } = makeUpsertDb([]);
    vi.mocked(database).mockReturnValue(db as never);

    await upsertRegularSeasonStandings([makeRecord()]);

    const inserted = values.mock.calls[0][0];
    expect(inserted[0].syncedAt).toBeInstanceOf(Date);
    expect(inserted[0].syncedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
  });
});

// ---------------------------------------------------------------------------
// upsertManualStanding
// ---------------------------------------------------------------------------

describe("upsertManualStanding", () => {
  it("sets syncedAt to null", async () => {
    const { db, values } = makeUpsertDb([{ id: "row-1" }]);
    vi.mocked(database).mockReturnValue(db as never);

    await upsertManualStanding("p-1", "ss-1", {
      wins: 10,
      losses: 5,
      gamesPlayed: 15,
    });

    const inserted = values.mock.calls[0][0];
    expect(inserted[0].syncedAt).toBeNull();
  });

  it("returns the first row", async () => {
    const row = { id: "row-1" };
    const { db } = makeUpsertDb([row]);
    vi.mocked(database).mockReturnValue(db as never);

    const result = await upsertManualStanding("p-1", "ss-1", {
      wins: 10,
      losses: 5,
      gamesPlayed: 15,
    });

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

// ---------------------------------------------------------------------------
// getRegularSeasonStandings — sorting logic
// ---------------------------------------------------------------------------

function makeRow(overrides: Record<string, unknown>) {
  return {
    conference: null,
    division: null,
    divisionRank: null,
    leagueRank: null,
    participant: {},
    ...overrides,
  };
}

function makeQueryDb(rows: object[]) {
  return {
    query: {
      regularSeasonStandings: {
        findMany: vi.fn().mockResolvedValue(rows),
      },
    },
  };
}

describe("getRegularSeasonStandings", () => {
  it("sorts by conference alphabetically", async () => {
    const rows = [
      makeRow({ conference: "Western", leagueRank: 1 }),
      makeRow({ conference: "Eastern", leagueRank: 2 }),
    ];
    vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);

    const result = await getRegularSeasonStandings("ss-1");
    expect(result[0].conference).toBe("Eastern");
    expect(result[1].conference).toBe("Western");
  });

  it("sorts by division within conference", async () => {
    const rows = [
      makeRow({ conference: "East", division: "Southeast", leagueRank: 1 }),
      makeRow({ conference: "East", division: "Atlantic", leagueRank: 2 }),
    ];
    vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);

    const result = await getRegularSeasonStandings("ss-1");
    expect(result[0].division).toBe("Atlantic");
    expect(result[1].division).toBe("Southeast");
  });

  it("sorts by divisionRank within division", async () => {
    const rows = [
      makeRow({ conference: "East", division: "Atlantic", divisionRank: 3 }),
      makeRow({ conference: "East", division: "Atlantic", divisionRank: 1 }),
      makeRow({ conference: "East", division: "Atlantic", divisionRank: 2 }),
    ];
    vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);

    const result = await getRegularSeasonStandings("ss-1");
    expect(result.map((r) => r.divisionRank)).toEqual([1, 2, 3]);
  });

  it("falls back to leagueRank when divisionRank is null", async () => {
    const rows = [
      makeRow({ leagueRank: 3 }),
      makeRow({ leagueRank: 1 }),
      makeRow({ leagueRank: 2 }),
    ];
    vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);

    const result = await getRegularSeasonStandings("ss-1");
    expect(result.map((r) => r.leagueRank)).toEqual([1, 2, 3]);
  });

  it("places null conference/division rows last", async () => {
    const rows = [
      makeRow({ conference: null, leagueRank: 1 }),
      makeRow({ conference: "Eastern", leagueRank: 2 }),
    ];
    vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);

    const result = await getRegularSeasonStandings("ss-1");
    expect(result[0].conference).toBe("Eastern");
    expect(result[1].conference).toBeNull();
  });
});

// ---------------------------------------------------------------------------
// getLastSyncedAt
// ---------------------------------------------------------------------------

describe("getLastSyncedAt", () => {
  function makeSelectDb(lastSync: Date | null) {
    return {
      select: vi.fn().mockReturnValue({
        from: vi.fn().mockReturnValue({
          where: vi.fn().mockResolvedValue([{ lastSync }]),
        }),
      }),
    };
  }

  it("returns the max syncedAt date", async () => {
    const date = new Date("2025-03-01T00:00:00Z");
    vi.mocked(database).mockReturnValue(makeSelectDb(date) as never);

    const result = await getLastSyncedAt("ss-1");
    expect(result).toEqual(date);
  });

  it("returns null when no synced records exist", async () => {
    vi.mocked(database).mockReturnValue(makeSelectDb(null) as never);

    const result = await getLastSyncedAt("ss-1");
    expect(result).toBeNull();
  });
});

// ---------------------------------------------------------------------------
// deleteRegularSeasonStandings
// ---------------------------------------------------------------------------

describe("deleteRegularSeasonStandings", () => {
  it("calls delete on the DB", async () => {
    const where = vi.fn().mockResolvedValue(undefined);
    const del = vi.fn().mockReturnValue({ where });
    const db = { delete: del };
    vi.mocked(database).mockReturnValue(db as never);

    await deleteRegularSeasonStandings("ss-1");
    expect(del).toHaveBeenCalled();
    expect(where).toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
// getParticipantStanding
// ---------------------------------------------------------------------------

describe("getParticipantStanding", () => {
  it("returns the standing row when found", async () => {
    const row = { id: "row-1", participantId: "p-1" };
    const db = {
      query: {
        regularSeasonStandings: {
          findFirst: vi.fn().mockResolvedValue(row),
        },
      },
    };
    vi.mocked(database).mockReturnValue(db as never);

    const result = await getParticipantStanding("p-1", "ss-1");
    expect(result).toEqual(row);
  });

  it("returns undefined when not found", async () => {
    const db = {
      query: {
        regularSeasonStandings: {
          findFirst: vi.fn().mockResolvedValue(undefined),
        },
      },
    };
    vi.mocked(database).mockReturnValue(db as never);

    const result = await getParticipantStanding("p-99", "ss-1");
    expect(result).toBeUndefined();
  });
});
