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

vi.mock("~/lib/auth.server", () => ({
  auth: { api: { getSession: vi.fn() } },
}));
vi.mock("~/models/user", () => ({
  isUserAdmin: vi.fn(),
}));
vi.mock("~/models/tournament", () => ({
  getTournamentById: vi.fn(),
  updateTournamentStatus: vi.fn(),
}));
vi.mock("~/models/tournament-result", () => ({
  getTournamentResults: vi.fn(),
  upsertTournamentResult: vi.fn(),
}));
vi.mock("~/models/participant", () => ({
  findCanonicalParticipantsBySport: vi.fn(),
}));
vi.mock("~/services/sync-tournament-results", () => ({
  syncTournamentResults: vi.fn(),
}));
vi.mock("~/lib/logger", () => ({
  logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
}));

import { action } from "~/routes/admin.tournaments.$id";
import { auth } from "~/lib/auth.server";
import { isUserAdmin } from "~/models/user";
import {
  getTournamentById,
  updateTournamentStatus,
} from "~/models/tournament";
import { upsertTournamentResult } from "~/models/tournament-result";
import { syncTournamentResults } from "~/services/sync-tournament-results";

const TOURNAMENT_ID = "tournament-1";
const ADMIN_USER_ID = "admin-1";
const NON_ADMIN_USER_ID = "user-1";

function makeRequest(body: Record<string, string>) {
  const formData = new FormData();
  for (const [k, v] of Object.entries(body)) {
    formData.append(k, v);
  }
  return new Request(`http://localhost/admin/tournaments/${TOURNAMENT_ID}`, {
    method: "POST",
    body: formData,
  });
}

function makeActionArgs(request: Request) {
  return {
    request,
    params: { id: TOURNAMENT_ID },
    context: {} as never,
  } as unknown as Parameters<typeof action>[0];
}

describe("admin.tournaments.$id action", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("batch-upsert-results: upserts each row, marks tournament completed, and fans out", async () => {
    vi.mocked(auth.api.getSession).mockResolvedValue({
      user: { id: ADMIN_USER_ID },
    } as never);
    vi.mocked(isUserAdmin).mockResolvedValue(true);
    vi.mocked(getTournamentById).mockResolvedValue({
      id: TOURNAMENT_ID,
      sportId: "sport-1",
      name: "US Open",
      year: 2026,
      status: "scheduled",
    } as never);
    vi.mocked(upsertTournamentResult).mockResolvedValue({} as never);
    vi.mocked(updateTournamentStatus).mockResolvedValue({} as never);
    const syncReport = {
      tournamentId: TOURNAMENT_ID,
      windowsSynced: 2,
      windowsFailed: 0,
      failures: [],
    };
    vi.mocked(syncTournamentResults).mockResolvedValue(syncReport);

    const results = [
      { participantId: "p-1", placement: 1 },
      { participantId: "p-2", placement: 2 },
      { participantId: "p-3", placement: 3 },
    ];
    const request = makeRequest({
      intent: "batch-upsert-results",
      results: JSON.stringify(results),
    });

    const response = await action(makeActionArgs(request));

    expect(upsertTournamentResult).toHaveBeenCalledTimes(3);
    expect(upsertTournamentResult).toHaveBeenCalledWith({
      tournamentId: TOURNAMENT_ID,
      participantId: "p-1",
      placement: 1,
    });
    expect(updateTournamentStatus).toHaveBeenCalledWith(
      TOURNAMENT_ID,
      "completed"
    );
    expect(syncTournamentResults).toHaveBeenCalledWith(TOURNAMENT_ID);
    expect(response).toMatchObject({
      success: true,
      syncReport,
    });
  });

  it("does not re-update status when tournament is already completed", async () => {
    vi.mocked(auth.api.getSession).mockResolvedValue({
      user: { id: ADMIN_USER_ID },
    } as never);
    vi.mocked(isUserAdmin).mockResolvedValue(true);
    vi.mocked(getTournamentById).mockResolvedValue({
      id: TOURNAMENT_ID,
      sportId: "sport-1",
      name: "US Open",
      year: 2026,
      status: "completed",
    } as never);
    vi.mocked(upsertTournamentResult).mockResolvedValue({} as never);
    vi.mocked(syncTournamentResults).mockResolvedValue({
      tournamentId: TOURNAMENT_ID,
      windowsSynced: 1,
      windowsFailed: 0,
      failures: [],
    });

    const request = makeRequest({
      intent: "batch-upsert-results",
      results: JSON.stringify([{ participantId: "p-1", placement: 1 }]),
    });

    await action(makeActionArgs(request));

    expect(updateTournamentStatus).not.toHaveBeenCalled();
    expect(syncTournamentResults).toHaveBeenCalledWith(TOURNAMENT_ID);
  });

  it("non-admin user is forbidden (403)", async () => {
    vi.mocked(auth.api.getSession).mockResolvedValue({
      user: { id: NON_ADMIN_USER_ID },
    } as never);
    vi.mocked(isUserAdmin).mockResolvedValue(false);

    const request = makeRequest({
      intent: "batch-upsert-results",
      results: JSON.stringify([{ participantId: "p-1", placement: 1 }]),
    });

    await expect(action(makeActionArgs(request))).rejects.toMatchObject({
      status: 403,
    });

    expect(upsertTournamentResult).not.toHaveBeenCalled();
    expect(syncTournamentResults).not.toHaveBeenCalled();
  });

  it("missing tournament returns 404", async () => {
    vi.mocked(auth.api.getSession).mockResolvedValue({
      user: { id: ADMIN_USER_ID },
    } as never);
    vi.mocked(isUserAdmin).mockResolvedValue(true);
    vi.mocked(getTournamentById).mockResolvedValue(null);

    const request = makeRequest({
      intent: "batch-upsert-results",
      results: JSON.stringify([{ participantId: "p-1", placement: 1 }]),
    });

    await expect(action(makeActionArgs(request))).rejects.toMatchObject({
      status: 404,
    });
  });
});
