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

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

vi.mock("~/lib/email.server", () => ({
  sendEmail: vi.fn().mockResolvedValue({ error: null }),
  wrapInEmailTemplate: vi.fn((content: string) => `<html>${content}</html>`),
  emailParagraph: vi.fn((html: string) => `<p>${html}</p>`),
  emailButton: vi.fn((href: string, text: string) => `<a href="${href}">${text}</a>`),
  escapeHtml: vi.fn((s: string) => s),
}));

import { sendOnTheClockEmail } from "../draft-email.server";
import { sendEmail } from "~/lib/email.server";

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

const SEASON_ID = "season-1";
const LEAGUE_ID = "league-1";
const TEAM_ID = "team-1";
const TEAM_ID_2 = "team-2";
const OWNER_ID = "owner-1";

/** 2-team, 4-round draft: picks 1,2,3,4,5,6,7,8 */
const DRAFT_SLOTS_2_TEAM = [
  { teamId: TEAM_ID, draftOrder: 1 },
  { teamId: TEAM_ID_2, draftOrder: 2 },
];

function makeSeason(currentPickNumber: number, draftRounds = 4) {
  return {
    id: SEASON_ID,
    leagueId: LEAGUE_ID,
    currentPickNumber,
    draftRounds,
  };
}

function makeOwner(emailNotificationsEnabled = true) {
  return {
    id: OWNER_ID,
    email: "owner@example.com",
    draftEmailNotificationsEnabled: emailNotificationsEnabled,
  };
}

function makeMockDb(overrides: {
  season?: object | null;
  team?: object | null;
  autodraft?: object | null;
  league?: object | null;
  owner?: object | null;
} = {}) {
  const season = "season" in overrides ? overrides.season : makeSeason(1);
  const team = "team" in overrides ? overrides.team : { id: TEAM_ID, name: "Team One", ownerId: OWNER_ID };
  const autodraft = "autodraft" in overrides ? overrides.autodraft : null;
  const league = "league" in overrides ? overrides.league : { id: LEAGUE_ID, name: "Test League" };
  const owner = "owner" in overrides ? overrides.owner : makeOwner();

  return {
    query: {
      seasons: { findFirst: vi.fn().mockResolvedValue(season) },
      teams: { findFirst: vi.fn().mockResolvedValue(team) },
      autodraftSettings: { findFirst: vi.fn().mockResolvedValue(autodraft) },
      leagues: { findFirst: vi.fn().mockResolvedValue(league) },
      users: { findFirst: vi.fn().mockResolvedValue(owner) },
    },
  };
}

const BASE_PARAMS = {
  seasonId: SEASON_ID,
  totalTeams: 2,
  draftSlots: DRAFT_SLOTS_2_TEAM,
};

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

beforeEach(() => {
  vi.clearAllMocks();
  process.env.APP_URL = "https://test.brackt.com";
});

afterEach(() => {
  delete process.env.APP_URL;
});

describe("sendOnTheClockEmail", () => {
  it("sends an email with the correct subject and recipient", async () => {
    const db = makeMockDb({ season: makeSeason(1) });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).toHaveBeenCalledWith(
      expect.objectContaining({
        to: "owner@example.com",
        subject: "You're on the clock in Test League",
      })
    );
  });

  it("returns early when season is not found", async () => {
    const db = makeMockDb({ season: null });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("returns early when currentPickNumber exceeds totalPicks", async () => {
    // 2 teams × 4 rounds = 8 total picks; pick 9 is past the end
    const db = makeMockDb({ season: makeSeason(9, 4) });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("returns early when the team has no owner", async () => {
    const db = makeMockDb({ team: { id: TEAM_ID, name: "Team One", ownerId: null } });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("returns early when owner has email notifications disabled", async () => {
    const db = makeMockDb({ owner: makeOwner(false) });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("returns early when autodraft is enabled for the team", async () => {
    const db = makeMockDb({ autodraft: { isEnabled: true } });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("returns early when league is not found", async () => {
    const db = makeMockDb({ league: null });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("sends back-to-back subject when the same team picks consecutively (snake turnaround)", async () => {
    // In a 2-team snake draft, pick 2 (team 2 forward) is followed by pick 3
    // (team 2 again on the reversal). Team 2 picks back-to-back.
    const db = makeMockDb({
      season: makeSeason(2),
      team: { id: TEAM_ID_2, name: "Team Two", ownerId: OWNER_ID },
    });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).toHaveBeenCalledWith(
      expect.objectContaining({
        subject: "You have back-to-back picks in Test League",
      })
    );
  });

  it("sends a regular subject when the next pick goes to a different team", async () => {
    // Pick 1 is team 1; pick 2 is team 2 — not consecutive for team 1
    const db = makeMockDb({ season: makeSeason(1) });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).toHaveBeenCalledWith(
      expect.objectContaining({
        subject: "You're on the clock in Test League",
      })
    );
  });

  it("does not treat the final pick as consecutive even when it is the last slot", async () => {
    // Pick 8 is the last pick (2 teams × 4 rounds). No pick 9, so not consecutive.
    const db = makeMockDb({ season: makeSeason(8, 4) });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    expect(sendEmail).toHaveBeenCalledWith(
      expect.objectContaining({
        subject: expect.stringContaining("on the clock"),
      })
    );
  });

  it("includes a link to the draft room in the email", async () => {
    const db = makeMockDb({ season: makeSeason(1) });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    const call = vi.mocked(sendEmail).mock.calls[0][0];
    expect(call.html).toContain(
      `https://test.brackt.com/leagues/${LEAGUE_ID}/draft/${SEASON_ID}`
    );
  });

  it("logs an error but does not throw when sendEmail fails", async () => {
    const db = makeMockDb({ season: makeSeason(1) });
    vi.mocked(sendEmail).mockResolvedValueOnce({ error: new Error("network failure") });

    await expect(
      sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never })
    ).resolves.toBeUndefined();
  });

  it("fetches team, autodraft settings, and league in parallel", async () => {
    const order: string[] = [];
    const db = makeMockDb({ season: makeSeason(1) });

    db.query.teams.findFirst = vi.fn().mockImplementation(async () => {
      order.push("team");
      return { id: TEAM_ID, name: "Team One", ownerId: OWNER_ID };
    });
    db.query.autodraftSettings.findFirst = vi.fn().mockImplementation(async () => {
      order.push("autodraft");
      return null;
    });
    db.query.leagues.findFirst = vi.fn().mockImplementation(async () => {
      order.push("league");
      return { id: LEAGUE_ID, name: "Test League" };
    });

    await sendOnTheClockEmail({ ...BASE_PARAMS, db: db as never });

    // All three should be called; order may vary since they run in parallel
    expect(order).toContain("team");
    expect(order).toContain("autodraft");
    expect(order).toContain("league");
    expect(db.query.teams.findFirst).toHaveBeenCalledTimes(1);
    expect(db.query.autodraftSettings.findFirst).toHaveBeenCalledTimes(1);
    expect(db.query.leagues.findFirst).toHaveBeenCalledTimes(1);
  });
});
