import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
import { PrismaService } from '../../../prisma/prisma.service';
import { TelegramApiService, TgUpdate } from './telegram-api.service';
import { TgIncomingJobData } from '../processors/telegram-incoming.processor';

/**
 * Long-polling fallback for Telegram bots.
 * Used when webhook delivery is unreliable (e.g. hosting/CDN issues).
 *
 * On startup:
 *  - Deletes webhook for each active bot
 *  - Starts polling loop with getUpdates (30s timeout)
 *  - Enqueues each update into BullMQ for processing
 */
@Injectable()
export class TelegramPollingService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(TelegramPollingService.name);
  private running = false;
  private readonly pollers = new Map<
    string,
    { token: string; offset: number; abort: AbortController }
  >();

  constructor(
    private readonly prisma: PrismaService,
    private readonly telegramApi: TelegramApiService,
    @InjectQueue('telegram-bot-incoming')
    private readonly incomingQueue: Queue<TgIncomingJobData>,
  ) {}

  async onModuleInit() {
    const bots = await this.prisma.tgBot.findMany({
      where: { isActive: true },
      select: { id: true, token: true, username: true },
    });

    if (bots.length === 0) {
      this.logger.log('No active bots — polling not started');
      return;
    }

    this.running = true;

    for (const bot of bots) {
      // Delete webhook so we can use getUpdates
      await this.telegramApi.deleteWebhook(bot.token).catch(() => {});
      this.logger.log(`Starting polling for @${bot.username ?? bot.id}`);

      const abort = new AbortController();
      this.pollers.set(bot.id, { token: bot.token, offset: 0, abort });

      // Fire and forget — loop runs in background
      this.pollLoop(bot.id).catch((err) => {
        this.logger.error(`Poll loop crashed for ${bot.id}: ${err}`);
      });
    }
  }

  onModuleDestroy() {
    this.running = false;
    for (const [id, poller] of this.pollers) {
      poller.abort.abort();
      this.logger.log(`Stopped polling for ${id}`);
    }
    this.pollers.clear();
  }

  private async pollLoop(botId: string) {
    const poller = this.pollers.get(botId);
    if (!poller) return;

    while (this.running) {
      try {
        const updates = await this.getUpdates(poller.token, poller.offset, poller.abort.signal);

        for (const update of updates) {
          await this.incomingQueue.add(
            'process-update',
            { botId, update },
            {
              attempts: 3,
              backoff: { type: 'exponential', delay: 2000 },
              removeOnComplete: 100,
              removeOnFail: 500,
            },
          );
          // Confirm offset
          poller.offset = update.update_id + 1;
        }
      } catch (err: unknown) {
        if (!this.running) break;
        const msg = err instanceof Error ? err.message : String(err);
        this.logger.error(`Polling error for ${botId}: ${msg}`);
        // Back off on error
        await this.sleep(5000);
      }
    }
  }

  private async getUpdates(
    token: string,
    offset: number,
    signal: AbortSignal,
  ): Promise<TgUpdate[]> {
    const url = `https://api.telegram.org/bot${token}/getUpdates`;
    const params = new URLSearchParams({
      offset: String(offset),
      limit: '100',
      timeout: '30',
      allowed_updates: JSON.stringify(['message', 'callback_query']),
    });

    const res = await fetch(`${url}?${params.toString()}`, { signal });
    const data = (await res.json()) as { ok: boolean; result?: TgUpdate[] };

    if (!data.ok) {
      throw new Error(`getUpdates failed`);
    }

    return data.result ?? [];
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
