import { randomUUID } from 'crypto';
import { CategoryChannel, ChannelType, Client, Guild, PermissionFlagsBits } from 'discord.js';
import { settingsStore } from '../config/state';
import { logger } from '../utils/logger';

export type StatCounterType =
  | 'members_total'
  | 'members_humans'
  | 'members_bots'
  | 'boosts'
  | 'text_channels'
  | 'voice_channels'
  | 'roles';

export interface StatCounter {
  id: string;
  type: StatCounterType;
  label?: string;
  format?: string;
  channelId?: string;
}

export interface ServerStatsConfig {
  enabled: boolean;
  categoryId?: string;
  categoryName?: string;
  refreshMinutes: number;
  cleanupOrphans?: boolean;
  items: StatCounter[];
}

const STAT_META: Record<
  StatCounterType,
  {
    label: string;
    defaultFormat: string;
  }
> = {
  members_total: { label: 'Mitglieder', defaultFormat: '{label}: {value}' },
  members_humans: { label: 'Menschen', defaultFormat: '{label}: {value}' },
  members_bots: { label: 'Bots', defaultFormat: '{label}: {value}' },
  boosts: { label: 'Boosts', defaultFormat: '{label}: {value}' },
  text_channels: { label: 'Text Channels', defaultFormat: '{label}: {value}' },
  voice_channels: { label: 'Voice Channels', defaultFormat: '{label}: {value}' },
  roles: { label: 'Rollen', defaultFormat: '{label}: {value}' }
};

export class StatsService {
  private client: Client | null = null;
  private interval?: NodeJS.Timeout;
  private lastRun = new Map<string, number>();
  private syncLocks = new Map<string, Promise<void>>();

  public setClient(client: Client) {
    this.client = client;
  }

  public stop() {
    if (this.interval) clearInterval(this.interval);
    this.interval = undefined;
  }

  public startScheduler() {
    this.stop();
    this.interval = setInterval(() => this.tick(), 60 * 1000);
  }

  public async getConfig(guildId: string): Promise<ServerStatsConfig> {
    const cfg = settingsStore.get(guildId);
    const statsCfg = (cfg as any)?.serverStatsConfig || {};
    const enabled = (cfg as any)?.serverStatsEnabled ?? statsCfg.enabled ?? false;
    return this.normalizeConfig({ ...statsCfg, enabled });
  }

  public async saveConfig(guildId: string, config: Partial<ServerStatsConfig>) {
    return this.withGuildLock(guildId, async () => {
      const previous = await this.getConfig(guildId);
      const normalized = this.normalizeConfig({ ...previous, ...config });
      const synced = await this.syncGuild(guildId, normalized, previous);
      await settingsStore.set(guildId, { serverStatsEnabled: synced.enabled, serverStatsConfig: synced } as any);
      this.lastRun.set(guildId, Date.now());
      return synced;
    });
  }

  public async refreshGuild(guildId: string) {
    return this.withGuildLock(guildId, async () => {
      const cfg = await this.getConfig(guildId);
      if (!cfg.enabled) return cfg;
      const synced = await this.syncGuild(guildId, cfg, cfg);
      await settingsStore.set(guildId, { serverStatsEnabled: synced.enabled, serverStatsConfig: synced } as any);
      this.lastRun.set(guildId, Date.now());
      return synced;
    });
  }

  public async disableGuild(guildId: string) {
    await settingsStore.set(guildId, { serverStatsEnabled: false } as any);
  }

  private normalizeConfig(config: Partial<ServerStatsConfig>): ServerStatsConfig {
    const fallbackItems = Array.isArray(config.items) ? config.items : [];
    const items = fallbackItems
      .filter((i) => i && (i as any).type && STAT_META[(i as any).type as StatCounterType])
      .slice(0, 8)
      .map((i) => {
        const type = (i as any).type as StatCounterType;
        const meta = STAT_META[type];
        return {
          id: i.id || randomUUID(),
          type,
          label: i.label || meta.label,
          format: i.format || meta.defaultFormat,
          channelId: i.channelId
        } as StatCounter;
      });
    const refreshMinutes = Number.isFinite(config.refreshMinutes) ? Number(config.refreshMinutes) : 10;
    return {
      enabled: !!config.enabled,
      categoryId: config.categoryId || undefined,
      categoryName: config.categoryName || '📊 Server Stats',
      refreshMinutes: Math.max(1, Math.min(180, refreshMinutes)),
      cleanupOrphans: config.cleanupOrphans ?? false,
      items: items.length ? items : [this.defaultItem()]
    };
  }

  private defaultItem(): StatCounter {
    return {
      id: randomUUID(),
      type: 'members_total',
      label: STAT_META['members_total'].label,
      format: STAT_META['members_total'].defaultFormat
    };
  }

  private formatName(item: StatCounter, value: number) {
    const meta = STAT_META[item.type];
    const label = item.label || meta.label;
    const base = (item.format || meta.defaultFormat || '{label}: {value}')
      .replace('{label}', label)
      .replace('{value}', value.toLocaleString('de-DE'));
    return base.slice(0, 96);
  }

  private async syncGuild(guildId: string, cfg: ServerStatsConfig, previous?: ServerStatsConfig): Promise<ServerStatsConfig> {
    if (!this.client) return cfg;
    const guild = await this.client.guilds.fetch(guildId).catch(() => null);
    if (!guild) return cfg;
    const category = await this.ensureCategory(guild, cfg);
    const managedIds = new Set<string>();

    for (const item of cfg.items) {
      const value = this.computeValue(guild, item.type);
      const desiredName = this.formatName(item, value);
      let channel =
        (item.channelId && (await guild.channels.fetch(item.channelId).catch(() => null))) ||
        null;
      if (!channel) {
        channel = await guild.channels
          .create({
            name: desiredName,
            type: ChannelType.GuildVoice,
            parent: category?.id,
            permissionOverwrites: [{ id: guild.roles.everyone.id, deny: [PermissionFlagsBits.Connect] }],
            userLimit: 0,
            bitrate: 8000
          })
          .catch((err) => {
            logger.warn(`Failed to create stats channel in ${guild.id}: ${err?.message || err}`);
            return null;
          });
        if (channel) item.channelId = channel.id;
      } else if (channel.type === ChannelType.GuildVoice || channel.type === ChannelType.GuildStageVoice) {
        const needsParent = category && channel.parentId !== category.id;
        const overwritesMissing = !channel.permissionOverwrites.cache.some(
          (ow) => ow.id === guild.roles.everyone.id && ow.deny.has(PermissionFlagsBits.Connect)
        );
        const editData: any = { name: desiredName };
        if (needsParent) editData.parent = category.id;
        if (overwritesMissing) editData.permissionOverwrites = [{ id: guild.roles.everyone.id, deny: [PermissionFlagsBits.Connect] }];
        if ((channel as any).userLimit !== 0) editData.userLimit = 0;
        await channel.edit(editData).catch(() => undefined);
      }
      if (channel?.id) managedIds.add(channel.id);
    }

    if (cfg.cleanupOrphans && previous?.items) {
      for (const old of previous.items) {
        if (old.channelId && !managedIds.has(old.channelId)) {
          const ch = await guild.channels.fetch(old.channelId).catch(() => null);
          if (ch && ch.parentId === category?.id) {
            await ch.delete('Papo Server Stats entfernt').catch(() => undefined);
          }
        }
      }
    }

    return { ...cfg, categoryId: category?.id };
  }

  private async ensureCategory(guild: Guild, cfg: ServerStatsConfig) {
    if (cfg.categoryId) {
      const existing = await guild.channels.fetch(cfg.categoryId).catch(() => null);
      if (existing && existing.type === ChannelType.GuildCategory) return existing as CategoryChannel;
    }
    const name = cfg.categoryName || '📊 Server Stats';
    const found = guild.channels.cache.find(
      (c) => c.type === ChannelType.GuildCategory && c.name.toLowerCase() === name.toLowerCase()
    ) as CategoryChannel | undefined;
    if (found) return found;
    const created = await guild.channels
      .create({
        name,
        type: ChannelType.GuildCategory,
        permissionOverwrites: [{ id: guild.roles.everyone.id, deny: [PermissionFlagsBits.Connect] }]
      })
      .catch(() => null);
    return created as CategoryChannel | null;
  }

  private computeValue(guild: Guild, type: StatCounterType): number {
    switch (type) {
      case 'members_total':
        return guild.memberCount ?? 0;
      case 'members_humans': {
        const humans = guild.members.cache.filter((m) => !m.user.bot).size;
        return humans || Math.max(0, (guild.memberCount ?? 0) - guild.members.cache.filter((m) => m.user.bot).size);
      }
      case 'members_bots':
        return guild.members.cache.filter((m) => m.user.bot).size;
      case 'boosts':
        return guild.premiumSubscriptionCount ?? 0;
      case 'text_channels':
        return guild.channels.cache.filter((c) => c.isTextBased() && c.type !== ChannelType.GuildVoice && c.type !== ChannelType.GuildStageVoice).size;
      case 'voice_channels':
        return guild.channels.cache.filter((c) => c.isVoiceBased()).size;
      case 'roles':
        return guild.roles.cache.size;
      default:
        return guild.memberCount ?? 0;
    }
  }

  private async tick() {
    const now = Date.now();
    for (const guildId of settingsStore.all().keys()) {
      const cfg = await this.getConfig(guildId);
      if (!cfg.enabled) continue;
      const last = this.lastRun.get(guildId) || 0;
      const intervalMs = Math.max(1, cfg.refreshMinutes) * 60 * 1000;
      if (now - last < intervalMs) continue;
      await this.refreshGuild(guildId).catch(() => undefined);
    }
  }

  private async withGuildLock<T>(guildId: string, task: () => Promise<T>): Promise<T> {
    const waitFor = this.syncLocks.get(guildId) || Promise.resolve();
    const run = (async () => {
      await waitFor.catch(() => undefined);
      return task();
    })();
    this.syncLocks.set(guildId, run.then(() => undefined, () => undefined));
    try {
      return await run;
    } finally {
      const current = this.syncLocks.get(guildId);
      if (current === run) this.syncLocks.delete(guildId);
    }
  }
}
