import { Injectable, Logger } from '@nestjs/common';
import { deriveTelegramChatId } from './bot-token.util';

export type InternalInlineButton = {
  text: string;
  callbackData?: string;
  url?: string;
  webAppUrl?: string;
};

export type InternalReplyButton = {
  text: string;
  requestContact?: boolean;
  requestLocation?: boolean;
};

export type InternalReplyMarkup =
  | {
      kind: 'inline';
      rows: InternalInlineButton[][];
    }
  | {
      kind: 'reply';
      rows: InternalReplyButton[][];
      resizeKeyboard?: boolean;
      oneTimeKeyboard?: boolean;
      selective?: boolean;
      inputFieldPlaceholder?: string;
    }
  | {
      kind: 'remove';
      selective?: boolean;
    };

export type ParsedReplyMarkup = {
  internal: InternalReplyMarkup | null;
  telegram: Record<string, unknown> | null;
};

export type StoredReplyMarkupPayload = {
  replyMarkup: InternalReplyMarkup | null;
  telegramReplyMarkup: Record<string, unknown> | null;
};

export type TelegramMessageObject = {
  message_id: number;
  from: {
    id: number;
    is_bot: boolean;
    first_name: string;
    username?: string;
  };
  chat: {
    id: number;
    type: string;
    first_name?: string;
    username?: string;
  };
  date: number;
  edit_date?: number;
  text?: string;
  caption?: string;
  photo?: Array<{ file_id: string; width: number; height: number }>;
  document?: { file_id: string; file_name?: string };
  reply_markup?: Record<string, unknown>;
};

export type BotInboundInternalMessage = {
  updateId: number;
  botId: string;
  senderUserId: string;
  senderDisplayName: string;
  senderUsername?: string | null;
  telegramChatId: number;
  telegramMessageId: number;
  text?: string | null;
  createdAt: Date;
};

export type BotCallbackInternalEvent = {
  updateId: number;
  botId: string;
  callbackQueryId: string;
  senderUserId: string;
  senderDisplayName: string;
  senderUsername?: string | null;
  telegramChatId: number;
  callbackData: string;
  sourceMessage: TelegramMessageObject;
  createdAt: Date;
};

export type TelegramUpdate =
  | {
      update_id: number;
      message: TelegramMessageObject;
    }
  | {
      update_id: number;
      callback_query: {
        id: string;
        from: {
          id: number;
          is_bot: boolean;
          first_name: string;
          username?: string;
        };
        message: TelegramMessageObject;
        chat_instance: string;
        data?: string;
      };
    };

type StoredBotMessage = {
  telegramMsgId: number;
  text?: string | null;
  messageType?: string | null;
  mediaUrl?: string | null;
  createdAt: Date;
  editedAt?: Date | null;
  payload?: unknown;
};

@Injectable()
export class TelegramSerializerService {
  private readonly logger = new Logger(TelegramSerializerService.name);

  parseReplyMarkup(replyMarkup: unknown): ParsedReplyMarkup {
    const markup = this.coerceReplyMarkupInput(replyMarkup);
    if (!markup) {
      return { internal: null, telegram: null };
    }

    this.logger.debug(`Parsed Reply Markup: ${JSON.stringify(markup)}`);

    if (markup.remove_keyboard === true) {
      const internal: InternalReplyMarkup = {
        kind: 'remove',
        selective: markup.selective === true
      };
      return { internal, telegram: { remove_keyboard: true, ...(markup.selective ? { selective: true } : {}) } };
    }

    if (Array.isArray(markup.inline_keyboard)) {
      const rows = (markup.inline_keyboard as unknown[]).map((row) =>
        Array.isArray(row)
          ? row
              .map((button) => this.parseInlineButton(button))
              .filter((button): button is InternalInlineButton => Boolean(button))
          : []
      );

      const telegram = { inline_keyboard: this.toTelegramInlineKeyboard(rows) };
      return { internal: { kind: 'inline', rows }, telegram };
    }

    if (Array.isArray(markup.keyboard)) {
      const rows = (markup.keyboard as unknown[]).map((row) =>
        Array.isArray(row)
          ? row
              .map((button) => this.parseReplyButton(button))
              .filter((button): button is InternalReplyButton => Boolean(button))
          : []
      );

      const internal: InternalReplyMarkup = {
        kind: 'reply',
        rows,
        resizeKeyboard: markup.resize_keyboard === true,
        oneTimeKeyboard: markup.one_time_keyboard === true,
        selective: markup.selective === true,
        inputFieldPlaceholder:
          typeof markup.input_field_placeholder === 'string' ? markup.input_field_placeholder : undefined
      };

      const telegram: Record<string, unknown> = {
        keyboard: rows.map((row) =>
          row.map((button) => ({
            text: button.text,
            ...(button.requestContact ? { request_contact: true } : {}),
            ...(button.requestLocation ? { request_location: true } : {})
          }))
        )
      };
      if (internal.kind === 'reply') {
        if (internal.resizeKeyboard) telegram.resize_keyboard = true;
        if (internal.oneTimeKeyboard) telegram.one_time_keyboard = true;
        if (internal.selective) telegram.selective = true;
        if (internal.inputFieldPlaceholder) telegram.input_field_placeholder = internal.inputFieldPlaceholder;
      }

      return { internal, telegram };
    }

    return { internal: null, telegram: null };
  }

  /**
   * node-telegram-bot-api и другие клиенты часто передают reply_markup как JSON-строку
   * (особенно при application/x-www-form-urlencoded). Приводим к объекту Telegram API.
   */
  private coerceReplyMarkupInput(replyMarkup: unknown): Record<string, unknown> | null {
    let current: unknown = replyMarkup;

    for (let depth = 0; depth < 3; depth += 1) {
      if (current === null || current === undefined || current === '') {
        return null;
      }

      if (typeof current === 'string') {
        const trimmed = current.trim();
        if (!trimmed) return null;
        try {
          current = JSON.parse(trimmed) as unknown;
          continue;
        } catch {
          this.logger.warn('Не удалось распарсить reply_markup как JSON-строку');
          return null;
        }
      }

      if (typeof current === 'object' && !Array.isArray(current)) {
        return this.normalizeReplyMarkupObject(current as Record<string, unknown>);
      }

      return null;
    }

    this.logger.warn('reply_markup содержит слишком глубокую JSON-вложенность');
    return null;
  }

  private normalizeReplyMarkupObject(markup: Record<string, unknown>): Record<string, unknown> {
    const normalized: Record<string, unknown> = { ...markup };

    if (typeof normalized.inline_keyboard === 'string') {
      try {
        normalized.inline_keyboard = JSON.parse(normalized.inline_keyboard.trim()) as unknown;
      } catch {
        this.logger.warn('Не удалось распарсить inline_keyboard внутри reply_markup');
        delete normalized.inline_keyboard;
      }
    }

    if (typeof normalized.keyboard === 'string') {
      try {
        normalized.keyboard = JSON.parse(normalized.keyboard.trim()) as unknown;
      } catch {
        this.logger.warn('Не удалось распарсить keyboard внутри reply_markup');
        delete normalized.keyboard;
      }
    }

    return normalized;
  }

  toTelegramUpdate(internalMessage: BotInboundInternalMessage): TelegramUpdate {
    return {
      update_id: internalMessage.updateId,
      message: this.buildTextMessage({
        messageId: internalMessage.telegramMessageId,
        chatId: internalMessage.telegramChatId,
        chatType: 'private',
        from: this.buildUserFrom(internalMessage),
        date: internalMessage.createdAt,
        text: internalMessage.text ?? undefined
      })
    };
  }

  toTelegramCallbackUpdate(event: BotCallbackInternalEvent): TelegramUpdate {
    const firstName = event.senderDisplayName.trim() || 'User';
    const username = event.senderUsername?.trim() || undefined;
    return {
      update_id: event.updateId,
      callback_query: {
        id: event.callbackQueryId,
        from: {
          id: event.telegramChatId,
          is_bot: false,
          first_name: firstName,
          ...(username ? { username } : {})
        },
        message: event.sourceMessage,
        chat_instance: `${event.botId}:${event.senderUserId}`,
        data: event.callbackData
      }
    };
  }

  buildBotMessageObject(input: {
    message: StoredBotMessage;
    bot: { id: string; name: string; username: string };
    chat: { telegramChatId: bigint | number; chatType: string; recipientDisplayName: string; recipientUsername?: string | null };
  }): TelegramMessageObject {
    const chatId = Number(input.chat.telegramChatId);
    const payload = this.readPayload(input.message.payload);
    const replyMarkup = payload.telegramReplyMarkup ?? undefined;

    const base = {
      message_id: input.message.telegramMsgId,
      from: {
        id: Number(deriveTelegramChatId(input.bot.id)),
        is_bot: true,
        first_name: input.bot.name,
        username: input.bot.username
      },
      chat: {
        id: chatId,
        type: input.chat.chatType,
        first_name: input.chat.recipientDisplayName,
        ...(input.chat.recipientUsername ? { username: input.chat.recipientUsername } : {})
      },
      date: Math.floor(input.message.createdAt.getTime() / 1000),
      ...(input.message.editedAt ? { edit_date: Math.floor(input.message.editedAt.getTime() / 1000) } : {}),
      ...(replyMarkup ? { reply_markup: replyMarkup } : {})
    };

    const messageType = input.message.messageType ?? 'text';
    if (messageType === 'photo' && input.message.mediaUrl) {
      return {
        ...base,
        photo: [{ file_id: input.message.mediaUrl, width: 320, height: 240 }],
        ...(input.message.text ? { caption: input.message.text } : {})
      };
    }

    if (messageType === 'document' && input.message.mediaUrl) {
      return {
        ...base,
        document: {
          file_id: input.message.mediaUrl,
          file_name: this.extractFileName(input.message.mediaUrl)
        },
        ...(input.message.text ? { caption: input.message.text } : {})
      };
    }

    return {
      ...base,
      ...(input.message.text ? { text: input.message.text } : {})
    };
  }

  buildTextMessage(input: {
    messageId: number;
    chatId: number;
    chatType: string;
    from: { id: number; displayName: string; username?: string | null; isBot?: boolean };
    date: Date;
    text?: string;
    editDate?: Date | null;
    replyMarkup?: Record<string, unknown> | null;
  }): TelegramMessageObject {
    return {
      message_id: input.messageId,
      from: {
        id: input.from.id,
        is_bot: input.from.isBot ?? false,
        first_name: input.from.displayName,
        ...(input.from.username ? { username: input.from.username } : {})
      },
      chat: {
        id: input.chatId,
        type: input.chatType,
        first_name: input.from.displayName,
        ...(input.from.username ? { username: input.from.username } : {})
      },
      date: Math.floor(input.date.getTime() / 1000),
      ...(input.editDate ? { edit_date: Math.floor(input.editDate.getTime() / 1000) } : {}),
      ...(input.text ? { text: input.text } : {}),
      ...(input.replyMarkup ? { reply_markup: input.replyMarkup } : {})
    };
  }

  readPayload(payload: unknown): StoredReplyMarkupPayload {
    if (!payload || typeof payload !== 'object') {
      return { replyMarkup: null, telegramReplyMarkup: null };
    }
    const record = payload as {
      replyMarkup?: InternalReplyMarkup | null;
      telegramReplyMarkup?: Record<string, unknown> | null;
      reply_markup?: unknown;
    };
    if (!record.telegramReplyMarkup && record.reply_markup) {
      const parsed = this.parseReplyMarkup(record.reply_markup);
      return { replyMarkup: parsed.internal, telegramReplyMarkup: parsed.telegram };
    }
    return {
      replyMarkup: record.replyMarkup ?? null,
      telegramReplyMarkup: record.telegramReplyMarkup ?? null
    };
  }

  readPayloadAsParsed(payload: unknown): ParsedReplyMarkup {
    const stored = this.readPayload(payload);
    return { internal: stored.replyMarkup, telegram: stored.telegramReplyMarkup };
  }

  buildStoredPayload(parsed: ParsedReplyMarkup) {
    if (!parsed.internal && !parsed.telegram) return undefined;
    return {
      replyMarkup: parsed.internal,
      telegramReplyMarkup: parsed.telegram
    };
  }

  private buildUserFrom(input: {
    telegramChatId: number;
    senderDisplayName: string;
    senderUsername?: string | null;
  }) {
    const firstName = input.senderDisplayName.trim() || 'User';
    const username = input.senderUsername?.trim() || undefined;
    return {
      id: input.telegramChatId,
      displayName: firstName,
      username,
      isBot: false
    };
  }

  private parseInlineButton(raw: unknown): InternalInlineButton | null {
    if (!raw || typeof raw !== 'object') return null;
    const button = raw as Record<string, unknown>;
    const text = typeof button.text === 'string' ? button.text.trim() : '';
    if (!text) return null;

    const webApp = button.web_app as { url?: string } | undefined;
    return {
      text,
      callbackData: typeof button.callback_data === 'string' ? button.callback_data : undefined,
      url: typeof button.url === 'string' ? button.url : undefined,
      webAppUrl: typeof webApp?.url === 'string' ? webApp.url : undefined
    };
  }

  private parseReplyButton(raw: unknown): InternalReplyButton | null {
    if (!raw || typeof raw !== 'object') return null;
    const button = raw as Record<string, unknown>;
    const text = typeof button.text === 'string' ? button.text.trim() : '';
    if (!text) return null;
    return {
      text,
      requestContact: button.request_contact === true,
      requestLocation: button.request_location === true
    };
  }

  private toTelegramInlineKeyboard(rows: InternalInlineButton[][]) {
    return rows.map((row) =>
      row.map((button) => ({
        text: button.text,
        ...(button.callbackData ? { callback_data: button.callbackData } : {}),
        ...(button.url ? { url: button.url } : {}),
        ...(button.webAppUrl ? { web_app: { url: button.webAppUrl } } : {})
      }))
    );
  }

  private extractFileName(url: string) {
    try {
      const parsed = new URL(url);
      const segments = parsed.pathname.split('/').filter(Boolean);
      return segments[segments.length - 1] || 'document';
    } catch {
      return 'document';
    }
  }
}
