import type { DiscordChannelOption, DiscordEmojiOption, DiscordRoleOption } from '@nexumi/shared';
import { UpstreamError } from './api-errors';
import { DASHBOARD_CHANNEL_TYPES } from './discord-channel-types';
import { env } from './env';
import { redis } from './redis';

const DISCORD_API_BASE = 'https://discord.com/api/v10';
const CACHE_TTL_SECONDS = 60;

export async function discordBotFetch<T>(
  path: string,
  init?: { method?: string; body?: unknown }
): Promise<T> {
  const method = init?.method ?? 'GET';
  const response = await fetch(`${DISCORD_API_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Bot ${env.BOT_TOKEN}`,
      ...(init?.body !== undefined ? { 'Content-Type': 'application/json' } : {})
    },
    body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
    cache: 'no-store'
  });
  if (!response.ok) {
    const status =
      response.status === 401 || response.status === 403 || response.status === 429
        ? response.status
        : 502;
    throw new UpstreamError(`Discord API request to ${path} failed with status ${response.status}`, status);
  }
  if (response.status === 204) {
    return undefined as T;
  }
  return (await response.json()) as T;
}

interface DiscordChannelRest {
  id: string;
  name: string;
  type: number;
  parent_id?: string | null;
  position?: number;
}

interface DiscordRoleRest {
  id: string;
  name: string;
  color: number;
  position: number;
  managed?: boolean;
}

interface DiscordEmojiRest {
  id: string;
  name: string;
  animated?: boolean;
}

async function readCache<T>(cacheKey: string): Promise<T | null> {
  try {
    if (redis.status === 'wait') {
      await redis.connect();
    }
    const cached = await redis.get(cacheKey);
    if (!cached) {
      return null;
    }
    return JSON.parse(cached) as T;
  } catch {
    // Cache is best-effort; fall through to Discord API.
    return null;
  }
}

async function writeCache(cacheKey: string, value: unknown): Promise<void> {
  try {
    if (redis.status === 'wait') {
      await redis.connect();
    }
    await redis.set(cacheKey, JSON.stringify(value), 'EX', CACHE_TTL_SECONDS);
  } catch {
    // ignore cache write failures
  }
}

export async function listGuildChannelsForDashboard(guildId: string): Promise<DiscordChannelOption[]> {
  // v3: ignore empty cache hits (same bug class as roles empty-cache freeze)
  const cacheKey = `dashboard:guild:${guildId}:channels:v3`;
  const cached = await readCache<DiscordChannelOption[]>(cacheKey);
  if (cached && cached.length > 0) {
    return cached;
  }

  const channels = await discordBotFetch<DiscordChannelRest[]>(`/guilds/${guildId}/channels`);
  if (!Array.isArray(channels)) {
    throw new Error(`Discord API returned non-array channels for guild ${guildId}`);
  }

  const options = channels
    .filter((channel) => DASHBOARD_CHANNEL_TYPES.has(channel.type))
    .map((channel) => ({
      id: channel.id,
      name: channel.name,
      type: channel.type,
      parentId: channel.parent_id ?? null
    }))
    .sort((a, b) => a.name.localeCompare(b.name));

  if (options.length > 0) {
    await writeCache(cacheKey, options);
  }
  return options;
}

export async function listGuildRolesForDashboard(guildId: string): Promise<DiscordRoleOption[]> {
  // v2: invalidate any stale empty/corrupt cache entries from earlier bugs
  const cacheKey = `dashboard:guild:${guildId}:roles:v2`;
  const cached = await readCache<DiscordRoleOption[]>(cacheKey);
  if (cached && cached.length > 0) {
    return cached;
  }

  const roles = await discordBotFetch<DiscordRoleRest[]>(`/guilds/${guildId}/roles`);
  if (!Array.isArray(roles)) {
    throw new Error(`Discord API returned non-array roles for guild ${guildId}`);
  }

  const options = roles
    .filter((role) => role.name !== '@everyone')
    .map((role) => ({
      id: role.id,
      name: role.name,
      color: role.color,
      position: role.position
    }))
    .sort((a, b) => b.position - a.position);

  // Never cache an empty list — that freezes "Keine Treffer" for the TTL
  // after a transient Discord/API glitch.
  if (options.length > 0) {
    await writeCache(cacheKey, options);
  }
  return options;
}

export async function listGuildEmojisForDashboard(guildId: string): Promise<DiscordEmojiOption[]> {
  const cacheKey = `dashboard:guild:${guildId}:emojis:v1`;
  const cached = await readCache<DiscordEmojiOption[]>(cacheKey);
  if (cached && cached.length > 0) {
    return cached;
  }

  const emojis = await discordBotFetch<DiscordEmojiRest[]>(`/guilds/${guildId}/emojis`);
  if (!Array.isArray(emojis)) {
    throw new Error(`Discord API returned non-array emojis for guild ${guildId}`);
  }

  const options = emojis
    .filter((emoji) => Boolean(emoji.id) && Boolean(emoji.name))
    .map((emoji) => {
      const animated = Boolean(emoji.animated);
      return {
        id: emoji.id,
        name: emoji.name,
        animated,
        url: `https://cdn.discordapp.com/emojis/${emoji.id}.${animated ? 'gif' : 'png'}?size=64`
      };
    })
    .sort((a, b) => a.name.localeCompare(b.name));

  if (options.length > 0) {
    await writeCache(cacheKey, options);
  }
  return options;
}

export { CHANNEL_TYPES, channelPrefix, type ChannelKind } from './discord-channel-types';
