import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Logger } from '@nestjs/common';
import { Job } from 'bullmq';
import { Prisma } from '@prisma/client';
import { BotManagerService } from '../services/bot-manager.service';
import { SubscriberService } from '../services/subscriber.service';
import { ScenarioEngineService } from '../services/scenario-engine.service';
import { ReferralService } from '../services/referral.service';
import { BotWebhookService } from '../services/bot-webhook.service';
import { TelegramApiService, TgUpdate } from '../services/telegram-api.service';
import { PrismaService } from '../../../prisma/prisma.service';
import { BotScenario } from '../interfaces/scenario.interface';

export interface TgIncomingJobData {
  botId: string;
  update: TgUpdate;
}

@Processor('telegram-bot-incoming')
export class TelegramIncomingProcessor extends WorkerHost {
  private readonly logger = new Logger(TelegramIncomingProcessor.name);

  constructor(
    private readonly botManager: BotManagerService,
    private readonly subscribers: SubscriberService,
    private readonly engine: ScenarioEngineService,
    private readonly referralService: ReferralService,
    private readonly botWebhooks: BotWebhookService,
    private readonly telegramApi: TelegramApiService,
    private readonly prisma: PrismaService,
  ) {
    super();
  }

  async process(job: Job<TgIncomingJobData>): Promise<void> {
    const { botId, update } = job.data;

    try {
      const bot = await this.botManager.getWithToken(botId);
      if (!bot.isActive) return;

      if (update.message) {
        await this.handleMessage(bot, update.message);
      } else if (update.callback_query) {
        await this.handleCallbackQuery(bot, update.callback_query);
      }
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : String(err);
      this.logger.error(`Error processing update for bot ${botId}: ${message}`);
      throw err;
    }
  }

  private async handleMessage(
    bot: {
      id: string;
      clubId: string;
      token: string;
      username: string | null;
      scenario: { scenario: unknown } | null;
    },
    message: TgUpdate['message'],
  ) {
    if (!message) return;

    const chat = message.chat;
    const from = message.from;
    const text = message.text ?? '';

    // Handle /stop — unsubscribe
    if (text === '/stop') {
      const existing = await this.subscribers.findByBotAndChat(bot.id, String(chat.id));
      if (existing) {
        await this.subscribers.markUnsubscribed(bot.id, String(chat.id));
        await this.telegramApi.sendMessage(bot.token, {
          chat_id: chat.id,
          text: 'Вы отписались от бота. Чтобы подписаться снова, отправьте /start',
        });
        await this.botWebhooks
          .dispatch(bot.id, 'subscriber.blocked', {
            subscriber: { id: existing.id, chatId: existing.chatId, reason: 'stop_command' },
          })
          .catch(() => {});
      }
      return;
    }

    // Parse /start command and referral
    let source: string | undefined;
    let referrerId: string | undefined;

    if (text.startsWith('/start')) {
      const payload = text.replace('/start', '').trim();
      if (payload.startsWith('ref_')) {
        referrerId = payload.replace('ref_', '');
        source = 'referral';
      } else if (payload) {
        source = payload;
      }
    }

    // Find or create subscriber
    const { subscriber, isNew } = await this.subscribers.findOrCreate(
      bot.id,
      bot.clubId,
      chat,
      from,
      source,
      referrerId,
    );

    // Log incoming message
    await this.prisma.tgBotMessage.create({
      data: {
        botId: bot.id,
        subscriberId: subscriber.id,
        direction: 'IN',
        text: text || null,
        payload: message.contact
          ? ({ contact: message.contact } as unknown as Prisma.InputJsonValue)
          : Prisma.JsonNull,
        telegramMsgId: message.message_id,
      },
    });

    // Emit webhook events
    if (isNew) {
      await this.botWebhooks
        .dispatch(bot.id, 'subscriber.created', {
          subscriber: {
            id: subscriber.id,
            chatId: subscriber.chatId,
            firstName: subscriber.firstName,
          },
        })
        .catch(() => {});
    }

    // Handle contact sharing (phone)
    if (message.contact?.phone_number) {
      await this.subscribers.updatePhone(subscriber.id, message.contact.phone_number);
      await this.botWebhooks
        .dispatch(bot.id, 'subscriber.phone_shared', {
          subscriber: {
            id: subscriber.id,
            chatId: subscriber.chatId,
            phone: message.contact.phone_number,
          },
        })
        .catch(() => {});
    }

    // Execute scenario if bot has one
    if (bot.scenario?.scenario) {
      const scenario = bot.scenario.scenario as unknown as BotScenario;
      const botCtx = { id: bot.id, clubId: bot.clubId, token: bot.token, username: bot.username };

      // Self-referral protection: if user clicked their own link
      if (referrerId && referrerId === subscriber.id) {
        await this.telegramApi.sendMessage(bot.token, {
          chat_id: chat.id,
          text: '😊 Это ваша собственная реферальная ссылка — отправьте её другу!',
          parse_mode: 'HTML',
          reply_markup: this.telegramApi.buildInlineKeyboard([
            {
              text: '📤 Поделиться ссылкой',
              url: `https://t.me/share/url?url=${encodeURIComponent(`https://t.me/${bot.username ?? ''}?start=ref_${subscriber.id}`)}&text=${encodeURIComponent('Попробуй бесплатную тренировку! 🏋️')}`,
            },
          ]),
        });
        return;
      }

      // If new user came via referral — notify referrer and route to friendStartNode
      if (isNew && referrerId) {
        const friendStartNode = await this.referralService.onFriendJoined(
          bot.id,
          bot.token,
          referrerId,
          from?.first_name ?? 'Друг',
          scenario,
        );

        if (friendStartNode && scenario.nodes[friendStartNode]) {
          // Override: start friend from friendStartNode instead of default start
          const vars = (subscriber.variables as Record<string, unknown>) ?? {};
          vars['is_friend'] = true;
          vars['referrer_id'] = referrerId;
          await this.engine.processMessage(
            botCtx,
            bot.scenario.scenario,
            subscriber.id,
            String(chat.id),
            null, // force fresh start
            vars,
            { ...message, text: `/start ${friendStartNode}` }, // trick engine to go to friendStartNode
          );
          return;
        }
      }

      await this.engine.processMessage(
        botCtx,
        bot.scenario.scenario,
        subscriber.id,
        String(chat.id),
        subscriber.currentNodeId,
        (subscriber.variables as Record<string, unknown>) ?? {},
        message,
      );
    } else {
      // No scenario — send fallback
      await this.telegramApi.sendMessage(bot.token, {
        chat_id: chat.id,
        text: '👋 Добро пожаловать! Бот настраивается.',
      });
    }
  }

  private async handleCallbackQuery(
    bot: {
      id: string;
      clubId: string;
      token: string;
      username: string | null;
      scenario: { scenario: unknown } | null;
    },
    query: NonNullable<TgUpdate['callback_query']>,
  ) {
    // Answer callback to remove loading indicator
    await this.telegramApi.answerCallbackQuery(bot.token, query.id);

    if (!bot.scenario?.scenario || !query.from) return;

    const chatId = String(query.message?.chat.id ?? query.from.id);
    const subscriber = await this.subscribers.findByBotAndChat(bot.id, chatId);
    if (!subscriber) return;

    await this.engine.processCallback(
      { id: bot.id, clubId: bot.clubId, token: bot.token, username: bot.username },
      bot.scenario.scenario,
      subscriber.id,
      chatId,
      (subscriber.variables as Record<string, unknown>) ?? {},
      query,
    );
  }
}
