#include "engine_fulfillment.h"

#include <curl/curl.h>
#include <iostream>
#include <sstream>
#include <chrono>
#include <ctime>

namespace node669427 {

static std::string extract_json_field(const std::string& json, const std::string& key) {
    auto pos = json.find("\"" + key + "\"");
    if (pos == std::string::npos) return "";
    auto colon = json.find(':', pos);
    if (colon == std::string::npos) return "";
    auto start = json.find_first_of("\"", colon + 1);
    if (start == std::string::npos) {
        auto val_start = json.find_first_not_of(" \t", colon + 1);
        if (val_start == std::string::npos) return "";
        auto val_end = json.find_first_of(",}\r\n", val_start);
        if (val_end == std::string::npos) val_end = json.size();
        return json.substr(val_start, val_end - val_start);
    }
    auto end = json.find_first_of("\"", start + 1);
    if (end == std::string::npos) return "";
    return json.substr(start + 1, end - start - 1);
}

FulfillmentEngine::FulfillmentEngine(NodeConfig& config, VaultPtr vault)
    : config_(config), vault_(vault) {}

size_t FulfillmentEngine::curlWriteCb(void* contents, size_t size, size_t nmemb, std::string* userp) {
    userp->append((char*)contents, size * nmemb);
    return size * nmemb;
}

std::string FulfillmentEngine::generateTrackingId() {
    auto now = std::chrono::system_clock::now();
    auto ts = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
    std::ostringstream ss;
    ss << "PKG-" << ts << "-" << (++tracking_counter_);
    return ss.str();
}

std::string FulfillmentEngine::start(const std::string& body) {
    std::string destination = extract_json_field(body, "destination");
    std::string service = extract_json_field(body, "service");
    std::string origin = extract_json_field(body, "origin");
    std::string item_desc = extract_json_field(body, "item_description");

    if (destination.empty()) {
        return "{\"error\":\"missing_destination\"}";
    }

    std::string tracking_id = generateTrackingId();
    std::string service_name = service.empty() ? "canada_post_regular" : service;

    auto now = std::chrono::system_clock::now();
    auto t = std::chrono::system_clock::to_time_t(now);
    char buf[64];
    std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&t));

    {
        std::lock_guard<std::mutex> lock(records_mutex_);
        TrackingRecord rec;
        rec.tracking_id = tracking_id;
        rec.status = "initiated";
        rec.origin = origin;
        rec.destination = destination;
        rec.service = service_name;
        rec.created_at = buf;
        rec.updated_at = buf;
        records_[tracking_id] = rec;
    }

    std::string cp_api_key = vault_->get("canadapost_api_key");
    std::string cp_secret = vault_->get("canadapost_secret");

    if (!cp_api_key.empty() && !cp_secret.empty()) {
        std::string api_url = "https://soa-gw.canadapost.ca/shipments";
        std::string auth = cp_api_key + ":" + cp_secret;

        CURL* curl = curl_easy_init();
        if (curl) {
            std::string response;
            std::string auth_b64;
            BIO* bio = BIO_new(BIO_s_mem());
            BIO* b64 = BIO_new(BIO_f_base64());
            bio = BIO_push(b64, bio);
            BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
            BIO_write(bio, auth.data(), static_cast<int>(auth.size()));
            BIO_flush(bio);
            BUF_MEM* buffer_ptr;
            BIO_get_mem_ptr(bio, &buffer_ptr);
            auth_b64 = std::string(buffer_ptr->data, buffer_ptr->length);
            BIO_free_all(bio);

            struct curl_slist* headers = nullptr;
            headers = curl_slist_append(headers, "Content-Type: application/json");
            headers = curl_slist_append(headers, ("Authorization: Basic " + auth_b64).c_str());

            std::string post_data = "{\"destination\":\"" + destination +
                                    "\",\"service\":\"" + service_name +
                                    "\",\"item\":\"" + item_desc + "\"}";

            curl_easy_setopt(curl, CURLOPT_URL, api_url.c_str());
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCb);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
            curl_easy_perform(curl);
            curl_slist_free_all(headers);
            curl_easy_cleanup(curl);
        }
    }

    return "{\"status\":\"initiated\",\"tracking_id\":\"" + tracking_id +
           "\",\"service\":\"" + service_name +
           "\",\"destination\":\"" + destination + "\"}";
}

std::string FulfillmentEngine::track(const std::string& tracking_id) {
    std::lock_guard<std::mutex> lock(records_mutex_);
    auto it = records_.find(tracking_id);
    if (it == records_.end()) {
        return "{\"error\":\"tracking_not_found\",\"id\":\"" + tracking_id + "\"}";
    }
    std::ostringstream json;
    json << "{\"tracking_id\":\"" << it->second.tracking_id << "\""
         << ",\"status\":\"" << it->second.status << "\""
         << ",\"origin\":\"" << it->second.origin << "\""
         << ",\"destination\":\"" << it->second.destination << "\""
         << ",\"service\":\"" << it->second.service << "\""
         << ",\"created_at\":\"" << it->second.created_at << "\""
         << ",\"updated_at\":\"" << it->second.updated_at << "\"}";
    return json.str();
}

std::string FulfillmentEngine::updateStatus(const std::string& tracking_id, const std::string& status) {
    std::lock_guard<std::mutex> lock(records_mutex_);
    auto it = records_.find(tracking_id);
    if (it == records_.end()) {
        return "{\"error\":\"tracking_not_found\"}";
    }
    it->second.status = status;
    auto now = std::chrono::system_clock::now();
    auto t = std::chrono::system_clock::to_time_t(now);
    char buf[64];
    std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&t));
    it->second.updated_at = buf;
    return "{\"status\":\"updated\",\"tracking_id\":\"" + tracking_id +
           "\",\"new_status\":\"" + status + "\"}";
}

} // namespace node669427
