import { createHash } from 'crypto';
import { env } from './env';
import { redis } from './redis';

const DISCORD_API_BASE = 'https://discord.com/api/v10';
const OAUTH_SCOPES = ['identify', 'guilds'] as const;
const USER_GUILDS_CACHE_TTL_SECONDS = 30;

export function getRedirectUri(): string {
  return `${env.WEBUI_URL}/api/auth/callback`;
}

export function buildAuthorizeUrl(state: string): string {
  const url = new URL('https://discord.com/api/oauth2/authorize');
  url.searchParams.set('client_id', env.BOT_CLIENT_ID);
  url.searchParams.set('redirect_uri', getRedirectUri());
  url.searchParams.set('response_type', 'code');
  url.searchParams.set('scope', OAUTH_SCOPES.join(' '));
  url.searchParams.set('state', state);
  url.searchParams.set('prompt', 'consent');
  return url.toString();
}

export interface DiscordTokenResponse {
  access_token: string;
  token_type: string;
  expires_in: number;
  refresh_token: string;
  scope: string;
}

export async function exchangeCodeForToken(code: string): Promise<DiscordTokenResponse> {
  const body = new URLSearchParams({
    client_id: env.BOT_CLIENT_ID,
    client_secret: env.BOT_CLIENT_SECRET,
    grant_type: 'authorization_code',
    code,
    redirect_uri: getRedirectUri()
  });

  const response = await fetch(`${DISCORD_API_BASE}/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body,
    cache: 'no-store'
  });

  if (!response.ok) {
    throw new Error(`Discord token exchange failed with status ${response.status}`);
  }

  return (await response.json()) as DiscordTokenResponse;
}

export async function refreshDiscordToken(refreshToken: string): Promise<DiscordTokenResponse> {
  const body = new URLSearchParams({
    client_id: env.BOT_CLIENT_ID,
    client_secret: env.BOT_CLIENT_SECRET,
    grant_type: 'refresh_token',
    refresh_token: refreshToken
  });

  const response = await fetch(`${DISCORD_API_BASE}/oauth2/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body,
    cache: 'no-store'
  });

  if (!response.ok) {
    throw new Error(`Discord token refresh failed with status ${response.status}`);
  }

  return (await response.json()) as DiscordTokenResponse;
}

export interface DiscordUser {
  id: string;
  username: string;
  global_name: string | null;
  avatar: string | null;
  locale?: string;
}

export async function fetchDiscordUser(accessToken: string): Promise<DiscordUser> {
  const response = await fetch(`${DISCORD_API_BASE}/users/@me`, {
    headers: { Authorization: `Bearer ${accessToken}` },
    cache: 'no-store'
  });

  if (!response.ok) {
    throw new Error(`Failed to fetch Discord user with status ${response.status}`);
  }

  return (await response.json()) as DiscordUser;
}

export interface DiscordUserGuild {
  id: string;
  name: string;
  icon: string | null;
  owner: boolean;
  permissions: string;
}

export async function fetchDiscordUserGuilds(accessToken: string): Promise<DiscordUserGuild[]> {
  const response = await fetch(`${DISCORD_API_BASE}/users/@me/guilds`, {
    headers: { Authorization: `Bearer ${accessToken}` },
    cache: 'no-store'
  });

  if (!response.ok) {
    throw new Error(`Failed to fetch Discord guilds with status ${response.status}`);
  }

  return (await response.json()) as DiscordUserGuild[];
}

function guildsCacheKey(accessToken: string): string {
  return `webui:discordguilds:${createHash('sha256').update(accessToken).digest('hex')}`;
}

/**
 * Cached variant of {@link fetchDiscordUserGuilds} to avoid hitting Discord's
 * rate limits when a single dashboard page load needs the guild list more
 * than once (e.g. layout + page + server switcher).
 */
export async function fetchDiscordUserGuildsCached(accessToken: string): Promise<DiscordUserGuild[]> {
  const cacheKey = guildsCacheKey(accessToken);
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached) as DiscordUserGuild[];
  }
  const guilds = await fetchDiscordUserGuilds(accessToken);
  await redis.set(cacheKey, JSON.stringify(guilds), 'EX', USER_GUILDS_CACHE_TTL_SECONDS);
  return guilds;
}
