import { ActivityType, Status } from 'discord.js';
import {
  BOT_STATUS_REDIS_KEY,
  PRESENCE_REDIS_KEY,
  BotPresenceConfigSchema,
  BotStatusPayloadSchema,
  type BotPresenceConfig
} from '@nexumi/shared';
import { redis } from './redis.js';
import { logger } from './logger.js';
import type { BotContext } from './types.js';

const BOT_STATUS_SHARDS_HASH = 'bot:status:shards';
const BOT_STATUS_TTL_SECONDS = 120;

function shardStatusLabel(wsStatus: number): string {
  switch (wsStatus) {
    case Status.Ready:
      return 'ready';
    case Status.Connecting:
      return 'connecting';
    case Status.Reconnecting:
      return 'reconnecting';
    case Status.Idle:
      return 'idle';
    case Status.Nearly:
      return 'nearly';
    case Status.Disconnected:
      return 'disconnected';
    case Status.WaitingForGuilds:
      return 'waiting_for_guilds';
    case Status.Identifying:
      return 'identifying';
    case Status.Resuming:
      return 'resuming';
    default:
      return 'unknown';
  }
}

export async function publishBotStatus(context: BotContext): Promise<void> {
  const shardId = context.client.shard?.ids[0] ?? 0;
  const shardEntry = {
    id: shardId,
    status: shardStatusLabel(context.client.ws.status),
    guildCount: context.client.guilds.cache.size,
    ping: context.client.ws.ping
  };

  await redis.hset(BOT_STATUS_SHARDS_HASH, String(shardId), JSON.stringify(shardEntry));
  await redis.expire(BOT_STATUS_SHARDS_HASH, BOT_STATUS_TTL_SECONDS);

  const all = await redis.hgetall(BOT_STATUS_SHARDS_HASH);
  const shards = Object.values(all)
    .map((raw) => {
      try {
        return JSON.parse(raw) as {
          id: number;
          status: string;
          guildCount: number;
          ping: number;
        };
      } catch {
        return null;
      }
    })
    .filter((entry): entry is NonNullable<typeof entry> => entry !== null)
    .sort((a, b) => a.id - b.id);

  if (shards.length === 0) {
    return;
  }

  const payload = BotStatusPayloadSchema.parse({
    shards,
    apiLatencyMs: Math.round(shards.reduce((sum, shard) => sum + shard.ping, 0) / shards.length),
    guildCount: shards.reduce((sum, shard) => sum + shard.guildCount, 0),
    uptimeSeconds: Math.floor(process.uptime()),
    updatedAt: new Date().toISOString()
  });

  await redis.set(BOT_STATUS_REDIS_KEY, JSON.stringify(payload), 'EX', BOT_STATUS_TTL_SECONDS);
}

const ACTIVITY_TYPE_MAP: Record<BotPresenceConfig['activityType'], ActivityType> = {
  Playing: ActivityType.Playing,
  Listening: ActivityType.Listening,
  Watching: ActivityType.Watching,
  Competing: ActivityType.Competing
};

export async function readPresenceConfig(context: BotContext): Promise<BotPresenceConfig> {
  const cached = await redis.get(PRESENCE_REDIS_KEY);
  if (cached) {
    const parsed = BotPresenceConfigSchema.safeParse(JSON.parse(cached));
    if (parsed.success) {
      return parsed.data;
    }
  }

  const row = await context.prisma.botPresenceConfig.upsert({
    where: { id: 'singleton' },
    create: { id: 'singleton' },
    update: {}
  });
  const rotating = Array.isArray(row.rotatingMessages)
    ? row.rotatingMessages.filter((item: unknown): item is string => typeof item === 'string')
    : [];
  const config = BotPresenceConfigSchema.parse({
    status: row.status,
    activityType: row.activityType,
    activityText: row.activityText,
    rotatingMessages: rotating,
    maintenanceMode: row.maintenanceMode,
    maintenanceMessage: row.maintenanceMessage
  });
  await redis.set(PRESENCE_REDIS_KEY, JSON.stringify(config));
  return config;
}

export async function applyBotPresence(context: BotContext): Promise<void> {
  const config = await readPresenceConfig(context);

  if (config.maintenanceMode) {
    const text =
      config.maintenanceMessage?.trim() ||
      'Maintenance mode — please try again later.';
    await context.client.user?.setPresence({
      status: 'dnd',
      activities: [
        {
          name: text.slice(0, 128),
          type: ActivityType.Watching
        }
      ]
    });
    return;
  }

  const messages =
    config.rotatingMessages.length > 0 ? config.rotatingMessages : [config.activityText];
  const index = Math.floor(Date.now() / 60_000) % messages.length;
  const text = messages[index] ?? config.activityText;

  await context.client.user?.setPresence({
    status: config.status,
    activities: [
      {
        name: text,
        type: ACTIVITY_TYPE_MAP[config.activityType]
      }
    ]
  });
}

export async function isMaintenanceMode(context: BotContext): Promise<{
  enabled: boolean;
  message: string | null;
}> {
  const config = await readPresenceConfig(context);
  return {
    enabled: config.maintenanceMode,
    message: config.maintenanceMessage
  };
}

export async function runPresenceRefreshJob(context: BotContext): Promise<void> {
  try {
    await applyBotPresence(context);
  } catch (error) {
    logger.warn({ error }, 'Failed to apply bot presence');
  }
  try {
    await publishBotStatus(context);
  } catch (error) {
    logger.warn({ error }, 'Failed to publish bot status');
  }
}
