// TODO: move this into the stinger_utils_cpp project later.

#include <sstream>
#include <iomanip>
#include <algorithm>
#include <iterator>
#include <string>
#include <vector>
#include <stdexcept>
#include <cstdint>
#include <cstring>
#include <sstream>
#include "utils.hpp"
#include <algorithm>
#include <iterator>
#include "conversions.hpp"

// Utility function to convert ISO timestamp string to time_point
std::chrono::time_point<std::chrono::system_clock> parseIsoTimestamp(const std::string& isoTimestamp)
{
    std::tm tm = {};
    std::istringstream ss(isoTimestamp);

    // Parse ISO 8601 format (e.g., "2023-12-01T15:30:45Z" or "2023-12-01T15:30:45.123Z")
    ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");

    if (ss.fail())
    {
        throw std::runtime_error("Failed to parse ISO timestamp: " + isoTimestamp);
    }

    // Convert to time_t and then to time_point
    std::time_t time = std::mktime(&tm);
    return std::chrono::system_clock::from_time_t(time);
}

// Utility function to convert time_point to ISO timestamp string
std::string timePointToIsoString(const std::chrono::time_point<std::chrono::system_clock>& timePoint)
{
    std::time_t time = std::chrono::system_clock::to_time_t(timePoint);
    std::tm* tm = std::gmtime(&time);

    std::ostringstream ss;
    ss << std::put_time(tm, "%Y-%m-%dT%H:%M:%SZ");
    return ss.str();
}

std::string durationToIsoString(const std::chrono::duration<double>& duration)
{
    using namespace std::chrono;
    auto secs = duration_cast<seconds>(duration);
    auto millis = duration_cast<milliseconds>(duration - secs);

    std::ostringstream ss;
    ss << "PT";
    ss << secs.count();
    if (millis.count() > 0)
    {
        ss << "." << std::setw(3) << std::setfill('0') << millis.count();
    }
    ss << "S";
    return ss.str();
}

std::chrono::duration<double> parseIsoDuration(const std::string& isoDuration)
{
    // Only supports simple "PTnS" or "PTn.mS" (seconds, optional milliseconds)
    if (isoDuration.size() < 3 || isoDuration.substr(0, 2) != "PT" || isoDuration.back() != 'S')
    {
        throw std::runtime_error("Unsupported ISO 8601 duration format: " + isoDuration);
    }
    std::string numberPart = isoDuration.substr(2, isoDuration.size() - 3); // between PT and S
    double seconds = 0.0;
    try
    {
        seconds = std::stod(numberPart);
    }
    catch (const std::exception&)
    {
        throw std::runtime_error("Failed to parse ISO 8601 duration: " + isoDuration);
    }
    return std::chrono::duration<double>(seconds);
}

// Simple base64 implementation
static const std::string b64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static inline bool is_base64(unsigned char c) {
    return (std::isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64Encode(const std::vector<unsigned char>& bytes_to_encode) {
    std::string ret;
    int i = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];

    size_t in_len = bytes_to_encode.size();
    size_t pos = 0;

    while (in_len--) {
        char_array_3[i++] = bytes_to_encode[pos++];
        if (i == 3) {
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for(i = 0; (i <4) ; i++)
                ret += b64_chars[char_array_4[i]];
            i = 0;
        }
    }

    if (i)
    {
        for (int j = i; j < 3; j++)
            char_array_3[j] = '\0';

        char_array_4[0] = ( char_array_3[0] & 0xfc ) >> 2;
        char_array_4[1] = ( ( char_array_3[0] & 0x03 ) << 4 ) + ( ( char_array_3[1] & 0xf0 ) >> 4 );
        char_array_4[2] = ( ( char_array_3[1] & 0x0f ) << 2 ) + ( ( char_array_3[2] & 0xc0 ) >> 6 );
        char_array_4[3] = char_array_3[2] & 0x3f;

        for (int j = 0; (j < i + 1); j++)
            ret += b64_chars[char_array_4[j]];

        while((i++ < 3))
            ret += '=';

    }

    return ret;
}

std::vector<unsigned char> base64Decode(const std::string& encoded_string) {
    int in_len = encoded_string.size();
    int i = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::vector<unsigned char> ret;

    while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
        char_array_4[i++] = encoded_string[in_]; in_++;
        if (i ==4) {
            for (i = 0; i <4; i++)
                char_array_4[i] = b64_chars.find(char_array_4[i]);

            char_array_3[0] = ( char_array_4[0] << 2 ) + ( ( char_array_4[1] & 0x30 ) >> 4 );
            char_array_3[1] = ( ( char_array_4[1] & 0xf ) << 4 ) + ( ( char_array_4[2] & 0x3c ) >> 2 );
            char_array_3[2] = ( ( char_array_4[2] & 0x3 ) << 6 ) + char_array_4[3];

            for (i = 0; (i < 3); i++)
                ret.push_back(char_array_3[i]);
            i = 0;
        }
    }

    if (i) {
        for (int j = i; j <4; j++)
            char_array_4[j] = 0;

        for (int j = 0; j <4; j++)
            char_array_4[j] = b64_chars.find(char_array_4[j]);

        char_array_3[0] = ( char_array_4[0] << 2 ) + ( ( char_array_4[1] & 0x30 ) >> 4 );
        char_array_3[1] = ( ( char_array_4[1] & 0xf ) << 4 ) + ( ( char_array_4[2] & 0x3c ) >> 2 );
        char_array_3[2] = ( ( char_array_4[2] & 0x3 ) << 6 ) + char_array_4[3];

        for (int j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]);
    }

    return ret;
}
