#include "engine_fulfillment.h"
#include "json.hpp"

#include <curl/curl.h>
#include <iostream>
#include <sstream>
#include <chrono>
#include <ctime>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>

using json = nlohmann::json;

namespace node669427 {

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, service, origin, item_desc;
    try {
        json j = json::parse(body);
        destination = j.value("destination", "");
        service = j.value("service", "");
        origin = j.value("origin", "");
        item_desc = j.value("item_description", "");
    } catch (const json::exception&) {
        return json{{"error", "invalid_json_payload"}}.dump();
    }

    if (destination.empty()) {
        return json{{"error", "missing_destination"}}.dump();
    }

    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());

            json post_data = {
                {"destination", destination},
                {"service", service_name},
                {"item", item_desc}
            };
            std::string payload = post_data.dump();

            curl_easy_setopt(curl, CURLOPT_URL, api_url.c_str());
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCb);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
            
            // STRICT SSL ENFORCEMENT
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
            
            curl_easy_perform(curl);
            curl_slist_free_all(headers);
            curl_easy_cleanup(curl);
        }
    }

    return json{
        {"status", "initiated"},
        {"tracking_id", tracking_id},
        {"service", service_name},
        {"destination", destination}
    }.dump();
}

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 json{{"error", "tracking_not_found"}, {"id", tracking_id}}.dump();
    }
    
    return 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}
    }.dump();
}

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 json{{"error", "tracking_not_found"}}.dump();
    }
    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 json{
        {"status", "updated"},
        {"tracking_id", tracking_id},
        {"new_status", status}
    }.dump();
}

} // namespace node669427
