import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { PrismaService } from '../../infra/prisma.service';
import { BotDeliveryService } from './bot-delivery.service';
import { BotFatherAssistantService } from './bot-father-assistant.service';
import { BotFatherSeedService } from './bot-father-seed.service';
import { BotInboundPublisherService } from './bot-inbound-publisher.service';
import { deriveTelegramChatId, normalizeBotUsername } from './bot-token.util';
import { assertHumanAccount } from '../system-account.util';

@Injectable()
export class BotInboundService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly publisher: BotInboundPublisherService,
    private readonly delivery: BotDeliveryService,
    private readonly seed: BotFatherSeedService,
    private readonly assistant: BotFatherAssistantService
  ) {}
  async submitUserMessage(senderUserId: string, botRef: string, text: string, roomId?: string) {
    await this.assertHumanSender(senderUserId);

    const trimmedText = text.trim();
    if (!trimmedText) {
      throw new BadRequestException('Текст сообщения не может быть пустым');
    }

    const bot = await this.findBot(botRef);
    if (!bot || !bot.isActive) {
      throw new NotFoundException('Бот не найден или заблокирован');
    }

    const chat = roomId
      ? await this.resolveSharedFamilyBotChat(bot.id, senderUserId, roomId)
      : await this.ensureBotChat(bot.id, senderUserId);
    const event = await this.createMessageEvent(bot.id, chat.id, senderUserId, chat.telegramChatId, trimmedText);
    await this.dispatchEvent(event);
    if (bot.isSystemBot) {
      void this.assistant.handleUserMessage(bot.id, senderUserId, trimmedText).catch(() => undefined);
    }

    return {
      updateId: event.updateId,
      messageId: event.telegramMessageId,
      chatId: event.telegramChatId
    };
  }

  async submitCallbackQuery(senderUserId: string, botRef: string, messageId: number, callbackData: string) {
    await this.assertHumanSender(senderUserId);

    const trimmedData = callbackData.trim();
    if (!trimmedData) {
      throw new BadRequestException('callbackData не может быть пустым');
    }
    if (!Number.isFinite(messageId) || messageId <= 0) {
      throw new BadRequestException('messageId некорректен');
    }

    const bot = await this.findBot(botRef);
    if (!bot || !bot.isActive) {
      throw new NotFoundException('Бот не найден или заблокирован');
    }

    const chat = await this.ensureBotChat(bot.id, senderUserId);
    const sourceMessage = await this.prisma.botMessage.findFirst({
      where: {
        botId: bot.id,
        botChatId: chat.id,
        telegramMsgId: messageId
      }
    });
    if (!sourceMessage) {
      throw new NotFoundException('Сообщение бота для callback не найдено');
    }

    const updateId = await this.allocateUpdateId(bot.id);
    const event = {
      eventType: 'callback_query' as const,
      botId: bot.id,
      senderUserId,
      botChatId: chat.id,
      telegramChatId: chat.telegramChatId.toString(),
      telegramMessageId: messageId,
      updateId,
      createdAt: new Date().toISOString(),
      callbackQueryId: randomUUID(),
      callbackData: trimmedData,
      sourceBotMessageId: messageId
    };

    await this.dispatchEvent(event);
    if (bot.isSystemBot) {
      void this.assistant.handleCallbackQuery(bot.id, senderUserId, trimmedData).catch(() => undefined);
    }

    return {
      updateId: event.updateId,
      callbackQueryId: event.callbackQueryId,
      messageId: event.telegramMessageId
    };
  }

  private async createMessageEvent(
    botId: string,
    botChatId: string,
    senderUserId: string,
    telegramChatId: bigint,
    text: string
  ) {
    return this.prisma.$transaction(async (tx) => {
      const [updatedBot, updatedChat] = await Promise.all([
        tx.bot.update({
          where: { id: botId },
          data: { nextUpdateId: { increment: 1 } },
          select: { nextUpdateId: true }
        }),
        tx.botChat.update({
          where: { id: botChatId },
          data: { nextInboundMessageId: { increment: 1 } },
          select: { nextInboundMessageId: true }
        })
      ]);

      const updateId = updatedBot.nextUpdateId - 1;
      const telegramMessageId = updatedChat.nextInboundMessageId - 1;

      await tx.botInboundMessage.create({
        data: {
          botId,
          botChatId,
          senderUserId,
          telegramMsgId: telegramMessageId,
          text
        }
      });

      return {
        eventType: 'message' as const,
        botId,
        senderUserId,
        text,
        botChatId,
        telegramChatId: telegramChatId.toString(),
        telegramMessageId,
        updateId,
        createdAt: new Date().toISOString()
      };
    });
  }

  private async allocateUpdateId(botId: string) {
    const updated = await this.prisma.bot.update({
      where: { id: botId },
      data: { nextUpdateId: { increment: 1 } },
      select: { nextUpdateId: true }
    });
    return updated.nextUpdateId - 1;
  }

  private async dispatchEvent(event: Parameters<BotInboundPublisherService['publish']>[0]) {
    const published = await this.publisher.publish(event);
    if (!published) {
      await this.delivery.deliverInboundEvent(event);
    }
  }

  private async assertHumanSender(userId: string) {
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { isSystemAccount: true, linkedBotId: true, deletedAt: true, status: true }
    });
    if (!user || user.deletedAt || user.status !== 'ACTIVE') {
      throw new NotFoundException('Отправитель не найден');
    }
    assertHumanAccount(user, { message: 'Системные учётные записи не могут взаимодействовать с ботами' });
  }

  private async findBot(botRef: string) {
    const normalized = normalizeBotUsername(botRef.replace(/_bot$/i, ''));
    return this.prisma.bot.findFirst({
      where: {
        OR: [{ id: botRef }, { username: botRef }, { username: `${normalized}_bot` }, { username: normalized }]
      },
      select: { id: true, isActive: true, isSystemBot: true }
    });
  }

  private async ensureBotChat(botId: string, recipientUserId: string) {
    const existing = await this.prisma.botChat.findUnique({
      where: { botId_recipientUserId: { botId, recipientUserId } }
    });
    if (existing) return existing;

    const telegramChatId = deriveTelegramChatId(`${botId}:${recipientUserId}`);
    try {
      return await this.prisma.botChat.create({
        data: {
          botId,
          recipientUserId,
          telegramChatId,
          chatType: 'private'
        }
      });
    } catch {
      const fallback = await this.prisma.botChat.findUnique({
        where: { botId_recipientUserId: { botId, recipientUserId } }
      });
      if (!fallback) {
        throw new NotFoundException('Не удалось создать чат с ботом');
      }
      return fallback;
    }
  }

  private async resolveSharedFamilyBotChat(botId: string, senderUserId: string, roomId: string) {
    const room = await this.prisma.chatRoom.findUnique({
      where: { id: roomId },
      include: {
        members: { include: { user: { select: { linkedBotId: true } } } }
      }
    });
    if (!room || room.type !== 'BOT' || !room.members.some((member) => member.userId === senderUserId)) {
      throw new NotFoundException('Чат с ботом не найден');
    }
    const botMember = room.members.find((member) => member.user.linkedBotId === botId);
    if (!botMember || room.peerUserId !== botMember.userId) {
      throw new NotFoundException('Бот не найден в этом чате');
    }
    const ownerUserId =
      room.createdById ??
      room.members.find((member) => !member.user.linkedBotId)?.userId ??
      senderUserId;
    return this.ensureBotChat(botId, ownerUserId);
  }
}
