/**
 * Tests for executeAutoPick timer behavior across chess_clock and standard modes.
 *
 * chess_clock: auto-picks earn the increment (bank += increment) so a team that
 *              times out doesn't get permanently frozen at 0 on all future turns.
 * standard:    auto-picks reset the bank to exactly draftIncrementTime.
 */
import { describe, it, expect, vi, beforeEach } from "vitest";
import { executeAutoPick } from "../draft-utils";

// ── Module-level mocks ────────────────────────────────────────────────────────

vi.mock("~/server/socket", () => ({
  getSocketIO: vi.fn(),
}));

vi.mock("~/models/draft-pick", () => ({
  getDraftPicksWithSports: vi.fn(),
  getTeamDraftPicksWithSports: vi.fn(),
  isParticipantDrafted: vi.fn(),
}));

vi.mock("~/models/participant", () => ({
  getParticipantsForSeasonWithSports: vi.fn(),
}));

vi.mock("~/models/season-sport", () => ({
  getSeasonSportsSimple: vi.fn(),
}));

vi.mock("~/models/draft-queue", () => ({
  getTeamQueue: vi.fn(),
  getAllQueuesForSeason: vi.fn(),
}));

vi.mock("~/lib/draft-eligibility", () => ({
  calculateDraftEligibility: vi.fn(),
}));

// database context not needed — we pass db directly to executeAutoPick
vi.mock("~/database/context");

import { getDraftPicksWithSports, getTeamDraftPicksWithSports, isParticipantDrafted } from "~/models/draft-pick";
import { getParticipantsForSeasonWithSports } from "~/models/participant";
import { getSeasonSportsSimple } from "~/models/season-sport";
import { getTeamQueue, getAllQueuesForSeason } from "~/models/draft-queue";
import { calculateDraftEligibility } from "~/lib/draft-eligibility";

// ── Fixtures ──────────────────────────────────────────────────────────────────

const SEASON_ID = "season-1";
const TEAM_ID = "team-1";
const NEXT_TEAM_ID = "team-2";
const PARTICIPANT_ID = "p-1";
const SPORT_ID = "sport-1";

const mockParticipantForQueue = {
  id: PARTICIPANT_ID,
  name: "Test Player",
  sportsSeason: { sport: { id: SPORT_ID, name: "NFL" } },
};

const mockParticipantFull = {
  id: PARTICIPANT_ID,
  name: "Test Player",
  sportsSeason: {
    id: "ss-1",
    sport: { id: SPORT_ID, name: "NFL" },
  },
};

const mockDraftPick = {
  id: "pick-1",
  seasonId: SEASON_ID,
  teamId: TEAM_ID,
  participantId: PARTICIPANT_ID,
  pickNumber: 1,
  round: 1,
  pickInRound: 1,
  pickedByUserId: "",
  pickedByType: "auto",
};

const mockDraftSlots = [
  {
    id: "slot-1",
    seasonId: SEASON_ID,
    teamId: TEAM_ID,
    draftOrder: 1,
    team: { id: TEAM_ID, name: "Team 1", seasonId: SEASON_ID, ownerId: "owner-1" },
  },
  {
    id: "slot-2",
    seasonId: SEASON_ID,
    teamId: NEXT_TEAM_ID,
    draftOrder: 2,
    team: { id: NEXT_TEAM_ID, name: "Team 2", seasonId: SEASON_ID, ownerId: "owner-2" },
  },
];

function makeSeason(overrides: Record<string, unknown> = {}) {
  return {
    id: SEASON_ID,
    leagueId: "league-1",
    status: "draft",
    draftRounds: 3,
    draftInitialTime: 120,
    draftIncrementTime: 15,
    draftTimerMode: "chess_clock",
    currentPickNumber: 1,
    draftPaused: false,
    ...overrides,
  };
}

/**
 * Build a mock DB for executeAutoPick.
 *
 * The `returning` mock is left unconfigured so individual tests can set up
 * the sequence of values they expect (pick insert, then timer update).
 */
function makeMockDb(seasonOverrides: Record<string, unknown> = {}) {
  const mockDb: any = {
    query: {
      // Race-condition guard — no existing pick at this slot
      draftPicks: { findFirst: vi.fn().mockResolvedValue(null) },
      seasons: { findFirst: vi.fn().mockResolvedValue(makeSeason(seasonOverrides)) },
      draftSlots: { findMany: vi.fn().mockResolvedValue(mockDraftSlots) },
      participants: {
        // findMany: used by autoPickForTeam queue path
        findMany: vi.fn().mockResolvedValue([mockParticipantForQueue]),
        // findFirst: used by executeAutoPick to fetch full participant details
        findFirst: vi.fn().mockResolvedValue(mockParticipantFull),
      },
      draftTimers: {
        // Default: timer is at 0 (expired), which is the normal auto-pick trigger state
        findFirst: vi.fn().mockResolvedValue({ id: "timer-1", seasonId: SEASON_ID, teamId: TEAM_ID, timeRemaining: 0 }),
      },
      // checkAndTriggerNextAutodraft reads this; returning null prevents any recursion
      autodraftSettings: { findFirst: vi.fn().mockResolvedValue(null) },
    },
    insert: vi.fn().mockReturnThis(),
    values: vi.fn().mockReturnThis(),
    returning: vi.fn(),  // configured per-test
    update: vi.fn().mockReturnThis(),
    set: vi.fn().mockReturnThis(),
    where: vi.fn().mockReturnThis(),
    delete: vi.fn().mockReturnThis(),
  };
  return mockDb;
}

// ── Tests ──────────────────────────────────────────────────────────────────────

describe("executeAutoPick — timer mode behavior", () => {
  let mockSocketIO: any;

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

    mockSocketIO = { to: vi.fn().mockReturnThis(), emit: vi.fn() };
    const { getSocketIO } = await import("~/server/socket");
    vi.mocked(getSocketIO).mockReturnValue(mockSocketIO);

    // autoPickForTeam external dependencies — return minimal happy-path values
    vi.mocked(getDraftPicksWithSports).mockResolvedValue([]);
    vi.mocked(getTeamDraftPicksWithSports).mockResolvedValue([]);
    vi.mocked(getParticipantsForSeasonWithSports).mockResolvedValue([]);
    vi.mocked(getSeasonSportsSimple).mockResolvedValue([]);
    vi.mocked(calculateDraftEligibility).mockReturnValue({
      eligibleSportIds: new Set([SPORT_ID]),
      ineligibleReasons: {},
    } as ReturnType<typeof calculateDraftEligibility>);

    // Queue has one item so autoPickForTeam selects it without hitting the EV path
    vi.mocked(getTeamQueue).mockResolvedValue([
      { id: "q-1", participantId: PARTICIPANT_ID, queuePosition: 1 } as any,
    ]);
    vi.mocked(isParticipantDrafted).mockResolvedValue(false);

    // pruneIneligibleQueueItems calls getAllQueuesForSeason — return empty Map to skip pruning
    vi.mocked(getAllQueuesForSeason).mockResolvedValue(new Map() as any);
  });

  // ── chess_clock mode ────────────────────────────────────────────────────────

  describe("chess_clock mode", () => {
    it("emits timer-update with bank + increment after a timer-triggered pick", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "chess_clock", draftIncrementTime: 15 });
      // Timer expired at 0; 0 + 15 = 15 after the increment
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])  // insert draft pick
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 15 }]);  // update timer

      await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(mockSocketIO.emit).toHaveBeenCalledWith(
        "timer-update",
        expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 15 })
      );
    });

    it("writes the timer increment to the DB", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "chess_clock", draftIncrementTime: 15 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 15 }]);

      await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(mockDb.set).toHaveBeenCalledWith(
        expect.objectContaining({ timeRemaining: expect.anything(), updatedAt: expect.any(Date) })
      );
    });

    it("applies the configured increment, not a hard-coded default", async () => {
      // Use a non-default increment of 30 to confirm the value is read from the season
      const mockDb = makeMockDb({ draftTimerMode: "chess_clock", draftIncrementTime: 30 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);

      await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(mockSocketIO.emit).toHaveBeenCalledWith(
        "timer-update",
        expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 })
      );
    });

    it("returns success after the pick", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "chess_clock", draftIncrementTime: 15 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 15 }]);

      const result = await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(result.success).toBe(true);
    });
  });

  // ── standard mode ───────────────────────────────────────────────────────────

  describe("standard mode", () => {
    it("emits timer-update with exactly draftIncrementTime after a timer-triggered pick", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "standard", draftIncrementTime: 30 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);

      await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(mockSocketIO.emit).toHaveBeenCalledWith(
        "timer-update",
        expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 30 })
      );
    });

    it("writes the timer reset to the DB", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "standard", draftIncrementTime: 30 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);

      await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(mockDb.update).toHaveBeenCalledTimes(2);
    });

    it("uses custom draftIncrementTime when configured", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "standard", draftIncrementTime: 90 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 90 }]);

      await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(mockSocketIO.emit).toHaveBeenCalledWith(
        "timer-update",
        expect.objectContaining({ teamId: TEAM_ID, timeRemaining: 90 })
      );
    });

    it("returns success after the pick", async () => {
      const mockDb = makeMockDb({ draftTimerMode: "standard", draftIncrementTime: 30 });
      mockDb.returning
        .mockResolvedValueOnce([mockDraftPick])
        .mockResolvedValueOnce([{ id: "timer-1", teamId: TEAM_ID, timeRemaining: 30 }]);

      const result = await executeAutoPick({
        seasonId: SEASON_ID,
        teamId: TEAM_ID,
        pickNumber: 1,
        triggeredBy: "timer",
        autodraftSettings: null,
        db: mockDb,
      });

      expect(result.success).toBe(true);
    });
  });
});
