#include "dps/app.hpp"
#include "dps/config.hpp"

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>

#include <openssl/evp.h>
#include <zlib.h>

namespace fs = std::filesystem;

namespace {

void set_env(const char *name, const char *value) {
#ifdef _WIN32
    _putenv_s(name, value);
#else
    setenv(name, value, 1);
#endif
}

void clear_env(const char *name) {
#ifdef _WIN32
    _putenv_s(name, "");
#else
    unsetenv(name);
#endif
}

std::vector<unsigned char> decode_base64(const std::string &text) {
    std::string normalized = text;
    while (normalized.size() % 4 != 0) normalized.push_back('=');

    std::vector<unsigned char> decoded((normalized.size() / 4) * 3);
    const int size = EVP_DecodeBlock(decoded.data(),
                                     reinterpret_cast<const unsigned char *>(normalized.data()),
                                     static_cast<int>(normalized.size()));
    assert(size >= 0);

    std::size_t padding = 0;
    if (!normalized.empty() && normalized.back() == '=') ++padding;
    if (normalized.size() > 1 && normalized[normalized.size() - 2] == '=') ++padding;
    decoded.resize(static_cast<std::size_t>(size) - padding);
    return decoded;
}

std::string gunzip_bytes(const std::vector<unsigned char> &compressed) {
    z_stream stream{};
    stream.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(compressed.data()));
    stream.avail_in = static_cast<uInt>(compressed.size());

    if (inflateInit2(&stream, 15 + 16) != Z_OK) std::abort();

    std::string output;
    std::vector<char> buffer(4096);
    int ret = Z_OK;
    while (ret == Z_OK) {
        stream.next_out = reinterpret_cast<Bytef *>(buffer.data());
        stream.avail_out = static_cast<uInt>(buffer.size());
        ret = inflate(&stream, Z_NO_FLUSH);
        output.append(buffer.data(), buffer.size() - stream.avail_out);
    }
    inflateEnd(&stream);
    assert(ret == Z_STREAM_END);
    return output;
}

void test_crypto() {
    dps::LegacyCrypto crypto("unit-test-secret");

    const std::string password = "P@ssw0rd!";
    const std::string hashed = crypto.hash_password(password);
    assert(!hashed.empty());
    assert(hashed.rfind("$2", 0) == 0);
    assert(crypto.verify_password(password, hashed));
    assert(!crypto.verify_password(password, password));

    const std::string token = crypto.issue_jwt("tester", 60);
    assert(!token.empty());
    const auto subject = crypto.validate_jwt(token);
    assert(subject.has_value());
    assert(subject.value() == "tester");
    assert(!crypto.validate_jwt(token + "broken").has_value());

    const std::string gzip_payload = crypto.gzip_base64("{\"hello\":\"world\"}");
    assert(!gzip_payload.empty());
    const std::string inflated = gunzip_bytes(decode_base64(gzip_payload));
    assert(inflated == "{\"hello\":\"world\"}");

    const std::string rsa_tool = crypto.rsa_private_encrypt_tool("abc123");
    const std::string rsa_script = crypto.rsa_private_encrypt_script("xyz789");
    assert(!rsa_tool.empty());
    assert(!rsa_script.empty());
    assert(rsa_tool != "abc123");
    assert(rsa_script != "xyz789");
}

void test_config() {
    const fs::path path = fs::temp_directory_path() / "dps-config-smoke.conf";
    {
        std::ofstream out(path);
        out << "server.host=0.0.0.0\n";
        out << "server.port=8651\n";
        out << "server.trust_proxy=true\n";
        out << "server.proxy_header=x-real-ip\n";
        out << "database.host=127.0.0.1\n";
        out << "database.name=rindro\n";
        out << "database.user=root\n";
        out << "database.password=test\n";
        out << "database.ssl_mode=verify_ca\n";
        out << "database.ssl_ca=C:/certs/demo-ca.pem\n";
        out << "database.plugin_dir=C:/mariadb/plugin\n";
    }

    set_env("DPS_SERVER_HOST", "10.0.0.8");
    set_env("DPS_SERVER_PROXY_HEADER", "x-forwarded-for");
    set_env("DPS_DB_HOST", "192.168.0.10");
    set_env("DPS_DB_USER", "tester");
    set_env("DPS_DB_PASSWORD", "secret");
    set_env("DPS_DB_NAME", "demo");
    set_env("DPS_DB_SSL_MODE", "disable");
    set_env("DPS_DB_SSL_CA", "D:/certs/dev-ca.pem");
    set_env("DPS_DB_PLUGIN_DIR", "D:/mariadb/plugin");

    const dps::AppConfig config = dps::load_config(path.string());
    assert(config.server.host == "10.0.0.8");
    assert(config.server.port == 8651);
    assert(config.server.trust_proxy);
    assert(config.server.proxy_header == "x-forwarded-for");
    assert(config.database.host == "192.168.0.10");
    assert(config.database.user == "tester");
    assert(config.database.password == "secret");
    assert(config.database.name == "demo");
    assert(config.database.ssl_mode == "disable");
    assert(config.database.ssl_ca == "D:/certs/dev-ca.pem");
    assert(config.database.plugin_dir == "D:/mariadb/plugin");

    clear_env("DPS_SERVER_HOST");
    clear_env("DPS_SERVER_PROXY_HEADER");
    clear_env("DPS_DB_HOST");
    clear_env("DPS_DB_USER");
    clear_env("DPS_DB_PASSWORD");
    clear_env("DPS_DB_NAME");
    clear_env("DPS_DB_SSL_MODE");
    clear_env("DPS_DB_SSL_CA");
    clear_env("DPS_DB_PLUGIN_DIR");
    std::remove(path.string().c_str());
}

}  // namespace

int main() {
    test_crypto();
    test_config();
    return 0;
}
