#include "commands.h"
#include "common.h"
#include "curl/curl.h"
#include "curl/easy.h"
#include "td/telegram/td_api.h"
#include "td/tl/TlObject.h"
#include "util.h"
#include <cstring>
#include <functional>
#include <random>
#include <spdlog/spdlog.h>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

void send_help_msg(context *ctx, td_api::message &msg);

void cmd::handle_regular_message(context *ctx, td_api::message &msg) {
  if (msg.content_->get_id() == td_api::messageText::ID) {
    std::string text = static_cast<td_api::messageText&>(*msg.content_).text_->text_;
    if (std::strncmp(text.c_str(), "/shorten ", 9) == 0) {
      std::string_view param(text.begin() + 9, text.end());
      size_t nextSpace = param.find(' ');
      if (nextSpace != std::string_view::npos)
        param.remove_suffix(param.size() - nextSpace);
      spdlog::info("Command /shorten received with parameter '{}'", param);

      std::string paramStr(param);
      if (!url::is_url_valid(paramStr)) {
        send_help_msg(ctx, msg);
	return;
      }

      bool result = shorten_link(std::string(param), ctx, [tg = ctx->tg, chat_id = msg.chat_id_, thread_id = msg.message_thread_id_](std::string url){
        tg->send_query(td_api::make_object<td_api::sendMessage>(
          chat_id, thread_id,
          nullptr /*reply_to*/, nullptr /*options*/,
          nullptr, // reply_markup
          static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
            std::move(td_api::make_object<td_api::formattedText>(url, std::move(std::vector<td_api::object_ptr<td_api::textEntity>>()))),
            nullptr /*link_preview_options*/, false /*clear_draft*/
          ))
        ), {});
      });
      if (!result) {
        ctx->tg->send_query(td_api::make_object<td_api::sendMessage>(
          msg.chat_id_, msg.message_thread_id_,
          nullptr /*reply_to*/, nullptr /*options*/,
          nullptr, // reply_markup
          static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
            std::move(td_api::make_object<td_api::formattedText>("произошла какая-то ошибка :(", std::move(std::vector<td_api::object_ptr<td_api::textEntity>>()))),
            nullptr /*link_preview_options*/, false /*clear_draft*/
          ))
        ), {});
      }
    } else if (std::strncmp(text.c_str(), "/shorten", 8) == 0) {
      send_help_msg(ctx, msg);
    }
  }
}

void cmd::handle_inline_query(context *ctx, td_api::updateNewInlineQuery &query) {
  std::vector<td_api::object_ptr<td_api::InputInlineQueryResult>> results;
  if (url::is_url_valid(query.query_)) {
    uint64_t pendingQueryId = new_pending_query_id();
    ctx->inlineQueries.insert(std::pair<uint64_t, pending_inline_query>(pendingQueryId, {query.query_, std::chrono::steady_clock::now() + std::chrono::hours(5)}));

    auto messageButton = td_api::make_object<td_api::inlineKeyboardButton>(
      "press to shorten",
      static_cast<td_api::object_ptr<td_api::InlineKeyboardButtonType>>(td_api::make_object<td_api::inlineKeyboardButtonTypeCallback>(std::to_string(pendingQueryId)))
    );

    std::vector<decltype(messageButton)> messageButtonRow;
    messageButtonRow.push_back(std::move(messageButton));

    std::vector<decltype(messageButtonRow)> messageButtonRows;
    messageButtonRows.push_back(std::move(messageButtonRow));

    results.push_back(static_cast<td_api::object_ptr<td_api::InputInlineQueryResult>>(td_api::make_object<td_api::inputInlineQueryResultArticle>(
      "shorten",
      "",
      true, // hide_url
      "Shorten!",
      "...",
      "https://slavasil.ru/favicon.ico",
      48, 48,
      static_cast<td_api::object_ptr<td_api::ReplyMarkup>>(td_api::make_object<td_api::replyMarkupInlineKeyboard>(
        std::move(messageButtonRows)
      )),
      static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
        td_api::make_object<td_api::formattedText>(
          query.query_,
          std::move(std::vector<td_api::object_ptr<td_api::textEntity>>())
        ),
        td_api::make_object<td_api::linkPreviewOptions>(true, "", false, false, false),
        false // clear_draft
      ))
    )));
  }
  ctx->tg->send_query(td_api::make_object<td_api::answerInlineQuery>(
    query.id_,
    false, // is_personal
    nullptr, // button
    std::move(results),
    0, // cache_time
    "" // next_offset
  ), {});
}

void cmd::handle_callback_query(context *ctx, td_api::updateNewInlineCallbackQuery &query) {
  spdlog::info("callback query");
  if (query.payload_->get_id() != td_api::callbackQueryPayloadData::ID) return;

  auto payload = td::move_tl_object_as<td_api::callbackQueryPayloadData>(query.payload_);
  ctx->tg->send_query(td_api::make_object<td_api::answerCallbackQuery>(
    query.id_,
    payload->data_,
    false,
    "",
    0
  ), [](TgObject obj){
    if (obj->get_id() == td_api::error::ID) {
      spdlog::error("answerCallbackQuery error: {}", static_cast<td_api::error*>(obj.get())->message_);
    }
  });

  uint64_t queryId = std::stoull(payload->data_);
  spdlog::debug("is query in cache? {}", ctx->inlineQueries.contains(queryId));
  try {
    pending_inline_query &queryInfo = ctx->inlineQueries.at(queryId);
    shorten_link(queryInfo.text, ctx, [ctx, inlineMessageId = query.inline_message_id_](std::string url){
      ctx->tg->send_query(td_api::make_object<td_api::editInlineMessageText>(
        inlineMessageId,
        nullptr, // reply_markup
        static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
          td_api::make_object<td_api::formattedText>(
            url,
            std::move(std::vector<td_api::object_ptr<td_api::textEntity>>())
          ),
          td_api::make_object<td_api::linkPreviewOptions>(true, "", false, false, false),
          //nullptr,
          false
        ))
      ), {});
    });
    ctx->inlineQueries.erase(queryId);
  } catch (std::out_of_range) {
    spdlog::warn("a user tried to use an expired pending query");
    ctx->tg->send_query(td_api::make_object<td_api::editInlineMessageText>(
      query.inline_message_id_,
      nullptr, // reply_markup
      static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(
        td_api::make_object<td_api::formattedText>(
          "<сообщение устарело>",
          std::move(std::vector<td_api::object_ptr<td_api::textEntity>>())
        ),
        nullptr,
        false
      ))
    ), {});
  }
}

bool cmd::shorten_link(std::string link, context *ctx, std::function<void(std::string)> cb) {
  spdlog::debug("creating CURL request");
  CURL *req = curl_easy_init();
  if (!req) {
    return false;
  }
  char *escapedParam = curl_easy_escape(req, link.data(), link.size());
  std::string url("https://slavasil.ru/create?url=");
  url += escapedParam;
  curl_free(escapedParam);
  curl_easy_setopt(req, CURLOPT_URL, url.c_str());
  curl_easy_setopt(req, CURLOPT_WRITEFUNCTION, curl_receive_cb);
  curl_easy_setopt(req, CURLOPT_WRITEDATA, req);
  curl_easy_setopt(req, CURLOPT_PRIVATE, ctx);
  curl_easy_setopt(req, CURLOPT_FOLLOWLOCATION, 1);
  ctx->requests.emplace(req, [cb, req, ctx](active_request &r){
    std::string shortenedUrl(r.receivedData.data(), r.receivedData.size());
    spdlog::info("Received data from HTTP server: {}", shortenedUrl);
    cb(shortenedUrl);
    ctx->requests.erase(req);
  });
  CURLMcode r = curl_multi_add_handle(ctx->curl, req);
  return r == CURLM_OK;
}

void send_help_msg(context *ctx, td_api::message &msg) {
  std::string textRaw("usage: /shorten <url>");
  std::vector<td_api::object_ptr<td_api::textEntity>> empty;
  auto text = static_cast<td_api::object_ptr<td_api::InputMessageContent>>(td_api::make_object<td_api::inputMessageText>(td_api::make_object<td_api::formattedText>(textRaw, std::move(empty)), nullptr, false));
  ctx->tg->send_query(td_api::make_object<td_api::sendMessage>(msg.chat_id_, msg.message_thread_id_, nullptr, nullptr, nullptr, std::move(text)), {});
}

uint64_t cmd::new_pending_query_id() {
  static std::mt19937 rng;
  static std::uniform_int_distribution<uint64_t> dist(0, 0xFFFFFFFFFFFFFFFFULL);
  return dist(rng);
}
