#include "utils.h"
#include <sstream>
#include <iomanip>
#include <ctime>

int Utils::optionalStoiOrDefault(const std::unordered_map<std::string, std::string>& row,
                                 const std::string& key, int def) {
    auto it = row.find(key);
    if (it == row.end()) return def;
    const std::string& val = it->second;
    if (isNullOrEmpty(val)) return def;
    try {
        return std::stoi(val);
    } catch (...) {
        return def;
    }
}

double Utils::optionalStodOrDefault(const std::unordered_map<std::string, std::string>& row,
                                    const std::string& key, double def) {
    auto it = row.find(key);
    if (it == row.end()) return def;
    const std::string& val = it->second;
    if (isNullOrEmpty(val)) return def;
    try {
        return std::stod(val);
    } catch (...) {
        return def;
    }
}

bool Utils::isNullOrEmpty(const std::string& s) {
    return s.empty() || s == "NULL";
}

std::optional<std::chrono::system_clock::time_point> Utils::parseTimestamp(const std::string& iso) {
    std::istringstream ss(iso);
    std::tm tm = {};
    ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
    if (ss.fail()) {
        ss.clear();
        ss.str(iso);
        ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
        if (ss.fail()) return std::nullopt;
    }
    std::time_t time_c = std::mktime(&tm);
    if (time_c == -1) return std::nullopt;
    return std::chrono::system_clock::from_time_t(time_c);
}

std::optional<int> Utils::computeAgeYears(const std::string& birthdate_iso) {
    auto birth_tp = parseTimestamp(birthdate_iso);
    if (!birth_tp) return std::nullopt;
    auto now = std::chrono::system_clock::now();

    std::time_t birth_time = std::chrono::system_clock::to_time_t(*birth_tp);
    std::time_t now_time   = std::chrono::system_clock::to_time_t(now);

    std::tm birth_tm;
    std::tm now_tm;
#if defined(_WIN32) || defined(_WIN64)
    localtime_s(&birth_tm, &birth_time);
    localtime_s(&now_tm, &now_time);
#else
    localtime_r(&birth_time, &birth_tm);
    localtime_r(&now_time, &now_tm);
#endif

    int years = now_tm.tm_year - birth_tm.tm_year;
    if (now_tm.tm_mon < birth_tm.tm_mon ||
        (now_tm.tm_mon == birth_tm.tm_mon && now_tm.tm_mday < birth_tm.tm_mday)) {
        years--;
    }
    return years;
}

std::string Utils::buildPgIntArrayLiteral(const std::vector<int>& elems) {
    std::string res = "{";
    for (size_t i = 0; i < elems.size(); ++i) {
        res += std::to_string(elems[i]);
        if (i + 1 < elems.size()) res += ",";
    }
    res += "}";
    return res;
}

std::optional<int> Utils::optionalUid(const std::string& val) {
    if (isNullOrEmpty(val)) return std::nullopt;
    try {
        return std::stoi(val);
    } catch (...) {
        return std::nullopt;
    }
}
