import {
  Global,
  Inject,
  Logger,
  Module,
  type OnModuleDestroy,
  type OnModuleInit,
} from '@nestjs/common';
import { DiscordPlugin, parseDiscordInteractionBindings } from '@mosaicstack/discord-plugin';
import { TelegramPlugin } from '@mosaicstack/telegram-plugin';
import { PluginService } from './plugin.service.js';
import type { IChannelPlugin } from './plugin.interface.js';
import { PLUGIN_REGISTRY } from './plugin.tokens.js';

class DiscordChannelPluginAdapter implements IChannelPlugin {
  readonly name = 'discord';

  constructor(private readonly plugin: DiscordPlugin) {}

  async start(): Promise<void> {
    await this.plugin.start();
  }

  async stop(): Promise<void> {
    await this.plugin.stop();
  }

  async onProjectCreated(project: {
    id: string;
    name: string;
    description?: string;
  }): Promise<{ channelId: string } | null> {
    return this.plugin.createProjectChannel(project);
  }
}

class TelegramChannelPluginAdapter implements IChannelPlugin {
  readonly name = 'telegram';

  constructor(private readonly plugin: TelegramPlugin) {}

  async start(): Promise<void> {
    await this.plugin.start();
  }

  async stop(): Promise<void> {
    await this.plugin.stop();
  }
}

const DEFAULT_GATEWAY_URL = 'http://localhost:14242';

function requiredDiscordAllowlist(name: string): string[] {
  const value = process.env[name]
    ?.split(',')
    .map((id: string): string => id.trim())
    .filter((id: string): boolean => id.length > 0);
  if (!value || value.length === 0) {
    throw new Error(`${name} is required when DISCORD_BOT_TOKEN is configured`);
  }
  return value;
}

function createPluginRegistry(): IChannelPlugin[] {
  const plugins: IChannelPlugin[] = [];
  const discordToken = process.env['DISCORD_BOT_TOKEN'];
  const discordGuildId = process.env['DISCORD_GUILD_ID'];
  const discordGatewayUrl = process.env['DISCORD_GATEWAY_URL'] ?? DEFAULT_GATEWAY_URL;
  const discordServiceToken = process.env['DISCORD_SERVICE_TOKEN'];
  const discordServiceUserId = process.env['DISCORD_SERVICE_USER_ID'];

  if (discordToken) {
    if (!discordServiceToken || !discordServiceUserId) {
      throw new Error(
        'DISCORD_SERVICE_TOKEN and DISCORD_SERVICE_USER_ID are required when DISCORD_BOT_TOKEN is configured',
      );
    }
    plugins.push(
      new DiscordChannelPluginAdapter(
        new DiscordPlugin({
          token: discordToken,
          guildId: discordGuildId,
          gatewayUrl: discordGatewayUrl,
          serviceToken: discordServiceToken,
          allowedGuildIds: requiredDiscordAllowlist('DISCORD_ALLOWED_GUILD_IDS'),
          allowedChannelIds: requiredDiscordAllowlist('DISCORD_ALLOWED_CHANNEL_IDS'),
          allowedUserIds: requiredDiscordAllowlist('DISCORD_ALLOWED_USER_IDS'),
          interactionBindings: parseDiscordInteractionBindings(
            process.env['DISCORD_INTERACTION_BINDINGS'],
          ),
        }),
      ),
    );
  }

  const telegramToken = process.env['TELEGRAM_BOT_TOKEN'];
  const telegramGatewayUrl = process.env['TELEGRAM_GATEWAY_URL'] ?? DEFAULT_GATEWAY_URL;

  if (telegramToken) {
    plugins.push(
      new TelegramChannelPluginAdapter(
        new TelegramPlugin({
          token: telegramToken,
          gatewayUrl: telegramGatewayUrl,
        }),
      ),
    );
  }

  return plugins;
}

@Global()
@Module({
  providers: [
    {
      provide: PLUGIN_REGISTRY,
      useFactory: (): IChannelPlugin[] => createPluginRegistry(),
    },
    PluginService,
  ],
  exports: [PluginService, PLUGIN_REGISTRY],
})
export class PluginModule implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(PluginModule.name);

  constructor(@Inject(PLUGIN_REGISTRY) private readonly plugins: IChannelPlugin[]) {}

  async onModuleInit(): Promise<void> {
    for (const plugin of this.plugins) {
      this.logger.log(`Starting plugin: ${plugin.name}`);
      await plugin.start();
    }
  }

  async onModuleDestroy(): Promise<void> {
    for (const plugin of [...this.plugins].reverse()) {
      this.logger.log(`Stopping plugin: ${plugin.name}`);
      await plugin.stop();
    }
  }
}
