import { buildTiedRankChecker } from "~/lib/standings-display";

interface DiscordEmbed {
  title?: string;
  url?: string;
  description?: string;
  color?: number;
  footer?: { text: string };
}

interface DiscordWebhookPayload {
  content?: string;
  embeds?: DiscordEmbed[];
  allowed_mentions?: { parse: string[]; users: string[] };
}

export async function sendDiscordWebhook(
  webhookUrl: string,
  payload: DiscordWebhookPayload
): Promise<void> {
  const response = await fetch(webhookUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new Error(`Discord webhook failed: ${response.status} ${text}`);
  }
}

/** Escape characters that trigger Discord markdown formatting. */
function escapeMarkdown(text: string): string {
  return text.replace(/[_*~`|\\]/g, "\\$&");
}

export interface StandingEntry {
  teamId: string;
  teamName: string;
  username?: string;
  discordUserId?: string;
  totalPoints: number;
  rank: number;
}

export interface ScoredMatch {
  winnerName: string;
  loserName: string;
  winnerUsername?: string;
  loserUsername?: string;
  winnerDiscordUserId?: string;
  loserDiscordUserId?: string;
}

export async function sendStandingsUpdateNotification({
  webhookUrl,
  seasonName,
  standings,
  previousStandings,
  previousRanks,
  sportName,
  eventName,
  scoredMatches,
}: {
  webhookUrl: string;
  seasonName: string;
  standings: StandingEntry[];
  previousStandings: Map<string, number>;
  previousRanks?: Map<string, number>;
  sportName?: string;
  eventName?: string;
  scoredMatches?: ScoredMatch[];
}): Promise<void> {
  const sections: string[] = [];

  // Header: "Sport Name — Event Name"
  if (sportName || eventName) {
    const parts = [sportName, eventName].filter(Boolean);
    sections.push(`**${parts.join(" — ")}**`);
  }

  // Scored matches section — only show matches where at least one manager
  // (fantasy team owner) scored Brackt points or had a team eliminated.
  const relevantMatches = scoredMatches?.filter(
    (m) => m.winnerUsername !== undefined || m.loserUsername !== undefined
  );
  if (relevantMatches && relevantMatches.length > 0) {
    sections.push("\n**Scored Matches**");
    for (const match of relevantMatches) {
      const winnerManagerLabel = match.winnerDiscordUserId
        ? `<@${match.winnerDiscordUserId}>`
        : match.winnerUsername
          ? escapeMarkdown(match.winnerUsername)
          : undefined;
      const loserManagerLabel = match.loserDiscordUserId
        ? `<@${match.loserDiscordUserId}>`
        : match.loserUsername
          ? escapeMarkdown(match.loserUsername)
          : undefined;
      const winnerLabel = winnerManagerLabel
        ? `${escapeMarkdown(match.winnerName)} (${winnerManagerLabel})`
        : escapeMarkdown(match.winnerName);
      const loserLabel = loserManagerLabel
        ? `${escapeMarkdown(match.loserName)} (${loserManagerLabel})`
        : escapeMarkdown(match.loserName);
      sections.push(`• **${winnerLabel}** def. ${loserLabel}`);
    }
  }

  const isTied = buildTiedRankChecker(standings.map((s) => s.rank));
  const rankLabel = (rank: number) => (isTied(rank) ? `T${rank}` : `${rank}`);

  // Standings changes section — show teams whose points or rank changed.
  const changedTeams = standings.filter((s) => {
    const prevPoints = previousStandings.get(s.teamId);
    const pointsChanged = prevPoints !== undefined && prevPoints !== s.totalPoints;
    const prevRank = previousRanks?.get(s.teamId);
    const rankChanged = prevRank !== undefined && prevRank !== s.rank;
    return pointsChanged || rankChanged;
  });

  if (changedTeams.length > 0) {
    sections.push("\n**Standings Changes**");
    for (const s of changedTeams) {
      const rankPrefix = rankLabel(s.rank);
      const prevPoints = previousStandings.get(s.teamId);
      let pointDelta = "";
      if (prevPoints !== undefined && prevPoints !== s.totalPoints) {
        const diff = Math.round(s.totalPoints - prevPoints);
        const sign = diff > 0 ? "+" : "";
        pointDelta = ` **(${sign}${diff} pts)**`;
      }

      let rankDelta = "";
      if (previousRanks) {
        const prevRank = previousRanks.get(s.teamId);
        if (prevRank !== undefined && prevRank !== s.rank) {
          const moved = prevRank - s.rank; // positive = moved up
          rankDelta = moved > 0 ? ` ↑${moved}` : ` ↓${Math.abs(moved)}`;
        }
      }

      const escapedName = escapeMarkdown(s.teamName);
      const managerLabel = s.discordUserId
        ? `<@${s.discordUserId}>`
        : s.username
          ? escapeMarkdown(s.username)
          : undefined;
      const label = managerLabel ? `${escapedName} (${managerLabel})` : escapedName;
      sections.push(`${rankPrefix}\\. ${label} — ${Math.round(s.totalPoints)} pts${pointDelta}${rankDelta}`);
    }
  }

  const MAX_DESCRIPTION = 4096;
  let description = sections.join("\n");
  if (description.length > MAX_DESCRIPTION) {
    description = description.slice(0, MAX_DESCRIPTION - 3) + "...";
  }

  // Collect Discord user IDs of all opted-in managers appearing in this notification.
  const pingUserIds = new Set<string>();
  for (const s of changedTeams) {
    if (s.discordUserId) pingUserIds.add(s.discordUserId);
  }
  for (const m of relevantMatches ?? []) {
    if (m.winnerDiscordUserId) pingUserIds.add(m.winnerDiscordUserId);
    if (m.loserDiscordUserId) pingUserIds.add(m.loserDiscordUserId);
  }
  const pingIds = [...pingUserIds];

  const payload: DiscordWebhookPayload = {
    embeds: [
      {
        title: `📊 Standings Update — ${seasonName}`,
        description,
        color: 0x5865f2, // Discord blurple
        footer: { text: "brackt.com" },
      },
    ],
  };

  if (pingIds.length > 0) {
    // Discord caps allowed_mentions.users at 100; slice to avoid a rejected payload.
    const cappedIds = pingIds.slice(0, 100);
    payload.content = cappedIds.map((id) => `<@${id}>`).join(" ");
    payload.allowed_mentions = { parse: [], users: cappedIds };
  }

  await sendDiscordWebhook(webhookUrl, payload);
}

export async function sendPickAnnouncementNotification({
  webhookUrl,
  draftUrl,
  pickNumber,
  round,
  pickInRound,
  pickedTeamName,
  participantName,
  sportName,
  nextTeamName,
  nextOwnerDiscordId,
  isDraftComplete,
}: {
  webhookUrl: string;
  draftUrl: string;
  pickNumber: number;
  round: number;
  pickInRound: number;
  pickedTeamName: string;
  participantName: string;
  sportName: string;
  nextTeamName?: string;
  nextOwnerDiscordId?: string;
  isDraftComplete: boolean;
}): Promise<void> {
  const lines: string[] = [
    `**${escapeMarkdown(pickedTeamName)}** selected **${escapeMarkdown(participantName)}** (${escapeMarkdown(sportName)})`,
    `Round ${round}, Pick ${pickInRound}`,
  ];

  if (isDraftComplete) {
    lines.push("", "The draft is complete!");
  } else if (nextTeamName) {
    const nextLabel = nextOwnerDiscordId
      ? `<@${nextOwnerDiscordId}>`
      : escapeMarkdown(nextTeamName);
    lines.push("", `On the clock: **${escapeMarkdown(nextTeamName)}** (${nextLabel})`);
  }

  const description = lines.join("\n");
  const payload: DiscordWebhookPayload = {
    embeds: [
      {
        title: `Pick #${pickNumber} — Round ${round}, Pick ${pickInRound}`,
        url: draftUrl,
        description,
        color: 0x5865f2,
      },
    ],
  };

  if (nextOwnerDiscordId && !isDraftComplete) {
    payload.content = `<@${nextOwnerDiscordId}>`;
    payload.allowed_mentions = { parse: [], users: [nextOwnerDiscordId] };
  }

  await sendDiscordWebhook(webhookUrl, payload);
}

export async function sendDraftOrderNotification({
  webhookUrl,
  leagueName,
  leagueUrl,
  method,
  teams,
}: {
  webhookUrl: string;
  leagueName: string;
  leagueUrl: string;
  method: "manual" | "randomized";
  teams: Array<{ name: string; position: number; username?: string }>;
}): Promise<void> {
  const title =
    method === "manual"
      ? `Draft Order Manually Set — ${leagueName}`
      : `Draft Order Randomized — ${leagueName}`;

  const sorted = [...teams].sort((a, b) => a.position - b.position);
  let description = sorted
    .map((t) => {
      const teamLabel = escapeMarkdown(t.name);
      const usernameLabel = t.username ? ` (${escapeMarkdown(t.username)})` : "";
      return `${t.position}. ${teamLabel}${usernameLabel}`;
    })
    .join("\n");
  if (description.length > 4096) {
    description = description.slice(0, 4093) + "...";
  }

  await sendDiscordWebhook(webhookUrl, {
    embeds: [
      {
        title,
        url: leagueUrl,
        description,
        color: 0x5865f2,
      },
    ],
  });
}
