#pragma once

#include <curl/curl.h>

#include <stdexcept>
#include <string>

namespace agentgram {

inline size_t write_to_string(char* ptr, size_t size, size_t nmemb, void* userdata) {
  auto* out = static_cast<std::string*>(userdata);
  out->append(ptr, size * nmemb);
  return size * nmemb;
}

class Client {
 public:
  Client(std::string base_url, std::string token)
      : base_url_(std::move(base_url)), token_(std::move(token)) {
    if (!base_url_.empty() && base_url_.back() == '/') base_url_.pop_back();
    curl_global_init(CURL_GLOBAL_DEFAULT);
  }

  std::string updates(const std::string& since = "", int timeout_ms = 25000) {
    std::string path = "/api/agent/updates?timeout=" + std::to_string(timeout_ms);
    if (!since.empty()) path += "&since=" + since;
    return request("GET", path, "");
  }

  std::string send(const std::string& conversation_id,
                   const std::string& kind,
                   const std::string& text,
                   const std::string& data_json = "null",
                   const std::string& attachments_json = "[]") {
    std::string body = "{";
    body += "\"conversationId\":\"" + escape(conversation_id) + "\",";
    body += "\"kind\":\"" + escape(kind) + "\",";
    body += "\"text\":\"" + escape(text) + "\",";
    body += "\"data\":" + data_json + ",";
    body += "\"attachments\":" + attachments_json + ",";
    body += "\"meta\":{}";
    body += "}";
    return request("POST", "/api/agent/reply", body);
  }

  std::string reply(const std::string& conversation_id, const std::string& text) {
    return send(conversation_id, "text", text);
  }

 private:
  std::string request(const std::string& method, const std::string& path, const std::string& body) {
    CURL* curl = curl_easy_init();
    if (!curl) throw std::runtime_error("curl_easy_init failed");

    std::string response;
    std::string url = base_url_ + path;
    struct curl_slist* headers = nullptr;
    std::string auth = "authorization: Bearer " + token_;
    headers = curl_slist_append(headers, auth.c_str());
    headers = curl_slist_append(headers, "accept: application/json");
    headers = curl_slist_append(headers, "content-type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_string);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    if (method == "POST") {
      curl_easy_setopt(curl, CURLOPT_POST, 1L);
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    }
    CURLcode code = curl_easy_perform(curl);
    long status = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    if (code != CURLE_OK) throw std::runtime_error(curl_easy_strerror(code));
    if (status < 200 || status >= 300) throw std::runtime_error("Agentgram HTTP " + std::to_string(status) + ": " + response);
    return response;
  }

  static std::string escape(const std::string& input) {
    std::string out;
    for (char c : input) {
      switch (c) {
        case '\\': out += "\\\\"; break;
        case '"': out += "\\\""; break;
        case '\n': out += "\\n"; break;
        case '\r': out += "\\r"; break;
        case '\t': out += "\\t"; break;
        default: out += c;
      }
    }
    return out;
  }

  std::string base_url_;
  std::string token_;
};

}  // namespace agentgram
