import { All, Body, Controller, Get, HttpCode, Param, Post, Query, Req, Res } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
import { firstValueFrom, timeout } from 'rxjs';
import { CoreGrpcService } from '../core-grpc.service';

type HttpRequest = {
  method: string;
  query: Record<string, unknown>;
};

type HttpResponse = {
  status(code: number): HttpResponse;
  setHeader(name: string, value: string): void;
};

const botPathParam = {
  name: 'botPath',
  description: 'Путь токена в формате bot<token>, совместимо с Telegram Bot API.',
  example: 'bot123456:ABC-DEF'
};

const chatIdProperty = {
  oneOf: [{ type: 'string' }, { type: 'number' }],
  description: 'ID пользователя/чата в приложении или numeric chat_id из ответа Bot API.'
};

@ApiTags('Telegram Bot API')
@Controller()
export class TelegramBotApiController {
  constructor(private readonly core: CoreGrpcService) {}

  @Get(':botPath/getMe')
  @ApiOperation({
    summary: 'Bot API: getMe',
    description: 'Возвращает профиль бота в формате Telegram Bot API.'
  })
  @ApiParam(botPathParam)
  getMe(@Res({ passthrough: true }) response: HttpResponse, @Param('botPath') botPath: string) {
    return this.executeTelegramMethod(response, botPath, 'getMe', {});
  }

  @Post(':botPath/getChat')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Bot API: getChat',
    description: 'Возвращает приватный чат приложения и pinned_message, если сообщение закреплено.'
  })
  @ApiParam(botPathParam)
  @ApiBody({
    schema: {
      type: 'object',
      required: ['chat_id'],
      properties: {
        chat_id: chatIdProperty
      }
    }
  })
  getChatPost(
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Body() body: Record<string, unknown>
  ) {
    return this.executeTelegramMethod(response, botPath, 'getChat', body ?? {});
  }

  @Get(':botPath/getChat')
  @ApiOperation({
    summary: 'Bot API: getChat (GET)',
    description: 'GET-вариант метода getChat для клиентов, которые передают параметры в query.'
  })
  @ApiParam(botPathParam)
  getChatGet(
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Query() query: Record<string, unknown>
  ) {
    return this.executeTelegramMethod(response, botPath, 'getChat', query ?? {});
  }

  @Post(':botPath/sendMessage')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Bot API: sendMessage',
    description: 'Отправляет сообщение пользователю внутри приложения. Поддерживаются text, parse_mode и reply_markup.'
  })
  @ApiParam(botPathParam)
  @ApiBody({
    schema: {
      type: 'object',
      required: ['chat_id', 'text'],
      properties: {
        chat_id: chatIdProperty,
        text: { type: 'string' },
        parse_mode: { type: 'string', example: 'HTML' },
        reply_markup: { type: 'object', additionalProperties: true }
      }
    }
  })
  sendMessage(
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Body() body: Record<string, unknown>
  ) {
    return this.executeTelegramMethod(response, botPath, 'sendMessage', body ?? {});
  }

  @Post(':botPath/editMessageText')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Bot API: editMessageText',
    description: 'Редактирует текст ранее отправленного ботом сообщения.'
  })
  @ApiParam(botPathParam)
  @ApiBody({
    schema: {
      type: 'object',
      required: ['chat_id', 'message_id', 'text'],
      properties: {
        chat_id: chatIdProperty,
        message_id: { type: 'integer' },
        text: { type: 'string' },
        parse_mode: { type: 'string', example: 'HTML' },
        reply_markup: { type: 'object', additionalProperties: true }
      }
    }
  })
  editMessageText(
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Body() body: Record<string, unknown>
  ) {
    return this.executeTelegramMethod(response, botPath, 'editMessageText', body ?? {});
  }

  @Post(':botPath/pinChatMessage')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Bot API: pinChatMessage',
    description: 'Закрепляет сообщение бота в чате приложения.'
  })
  @ApiParam(botPathParam)
  @ApiBody({
    schema: {
      type: 'object',
      required: ['chat_id', 'message_id'],
      properties: {
        chat_id: chatIdProperty,
        message_id: { type: 'integer' },
        disable_notification: { type: 'boolean', default: false }
      }
    }
  })
  pinChatMessage(
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Body() body: Record<string, unknown>
  ) {
    return this.executeTelegramMethod(response, botPath, 'pinChatMessage', body ?? {});
  }

  @Post(':botPath/unpinChatMessage')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Bot API: unpinChatMessage',
    description: 'Открепляет указанное сообщение. Если message_id не передан, открепляет последнее закреплённое сообщение.'
  })
  @ApiParam(botPathParam)
  @ApiBody({
    schema: {
      type: 'object',
      required: ['chat_id'],
      properties: {
        chat_id: chatIdProperty,
        message_id: { type: 'integer' }
      }
    }
  })
  unpinChatMessage(
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Body() body: Record<string, unknown>
  ) {
    return this.executeTelegramMethod(response, botPath, 'unpinChatMessage', body ?? {});
  }

  @All(':botPath/:method')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Bot API: универсальный метод',
    description:
      'Совместимый endpoint /bot<TOKEN>/<method>. Поддерживаются getMe, getChat, sendMessage, editMessageText, editMessageReplyMarkup, sendPhoto, sendDocument, pinChatMessage, unpinChatMessage, answerCallbackQuery, setWebhook, deleteWebhook, getWebhookInfo, getUpdates и setChatMenuButton.'
  })
  @ApiParam(botPathParam)
  @ApiParam({
    name: 'method',
    description: 'Название метода Telegram Bot API.',
    example: 'sendMessage'
  })
  async handleTelegramMethod(
    @Req() request: HttpRequest,
    @Res({ passthrough: true }) response: HttpResponse,
    @Param('botPath') botPath: string,
    @Param('method') method: string,
    @Body() body: Record<string, unknown>
  ) {
    const payload = request.method === 'GET' ? { ...request.query } : body ?? {};
    return this.executeTelegramMethod(response, botPath, method, payload);
  }

  private async executeTelegramMethod(
    response: HttpResponse,
    botPath: string,
    method: string,
    payload: Record<string, unknown>
  ) {
    if (!botPath.startsWith('bot')) {
      response.status(404);
      return { ok: false, error_code: 404, description: 'Not Found' };
    }

    const token = decodeURIComponent(botPath.slice(3));
    if (!token) {
      response.status(401);
      return { ok: false, error_code: 401, description: 'Unauthorized' };
    }

    const grpcCall = this.core.bot.ExecuteBotMethod({
      token,
      method,
      payloadJson: JSON.stringify(payload)
    });

    const waitSeconds = method === 'getUpdates' ? Number(payload.timeout ?? 0) : 0;
    const grpcTimeoutMs = method === 'getUpdates' ? Math.min(Math.max(waitSeconds, 0), 50) * 1000 + 10_000 : 30_000;

    const result = (await firstValueFrom(
      grpcCall.pipe(timeout(grpcTimeoutMs)) as typeof grpcCall
    )) as { responseJson: string; httpStatus: number };

    response.status(result.httpStatus ?? 200);
    response.setHeader('Content-Type', 'application/json');
    return JSON.parse(result.responseJson);
  }
}
