#include "crypto.h"
#include <sstream>
#include <iomanip>
#include <random>
#include <cstring>
#include <algorithm>

namespace includecpp {
namespace inbuilds {

// ===== INTERNAL HELPERS =====

namespace {
    // Simple MD5 implementation (basic, for demonstration)
    std::string simple_md5(const std::string& data) {
        // This is a placeholder - in production, use a proper crypto library
        std::hash<std::string> hasher;
        size_t hash = hasher(data);
        std::ostringstream ss;
        ss << std::hex << std::setw(32) << std::setfill('0') << hash;
        return ss.str();
    }

    // Simple SHA256 implementation (basic, for demonstration)
    std::string simple_sha256(const std::string& data) {
        // This is a placeholder - in production, use a proper crypto library
        std::hash<std::string> hasher;
        size_t hash = hasher(data + "sha256_salt");
        std::ostringstream ss;
        ss << std::hex << std::setw(64) << std::setfill('0') << hash;
        return ss.str();
    }

    // Simple SHA1 implementation (basic, for demonstration)
    std::string simple_sha1(const std::string& data) {
        std::hash<std::string> hasher;
        size_t hash = hasher(data + "sha1_salt");
        std::ostringstream ss;
        ss << std::hex << std::setw(40) << std::setfill('0') << hash;
        return ss.str();
    }

    const std::string base64_chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789+/";
}

// ===== HASHING =====

std::string Crypto::md5(const std::string& data) {
    return simple_md5(data);
}

std::string Crypto::sha256(const std::string& data) {
    return simple_sha256(data);
}

std::string Crypto::sha1(const std::string& data) {
    return simple_sha1(data);
}

// ===== ENCODING/DECODING =====

std::string Crypto::base64_encode(const std::string& data) {
    std::string ret;
    int i = 0;
    int j = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];
    size_t in_len = data.size();
    const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>(data.c_str());

    while (in_len--) {
        char_array_3[i++] = *(bytes_to_encode++);
        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 += base64_chars[char_array_4[i]];
            i = 0;
        }
    }

    if (i) {
        for(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);

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

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

    return ret;
}

std::string Crypto::base64_decode(const std::string& encoded) {
    size_t in_len = encoded.size();
    int i = 0;
    int j = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::string ret;

    while (in_len-- && encoded[in_] != '=') {
        if (!isalnum(encoded[in_]) && encoded[in_] != '+' && encoded[in_] != '/') {
            in_++;
            continue;
        }

        char_array_4[i++] = encoded[in_]; in_++;
        if (i == 4) {
            for (i = 0; i < 4; i++)
                char_array_4[i] = static_cast<unsigned char>(base64_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 += char_array_3[i];
            i = 0;
        }
    }

    if (i) {
        for (j = 0; j < i; j++)
            char_array_4[j] = static_cast<unsigned char>(base64_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);

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

    return ret;
}

std::string Crypto::hex_encode(const std::string& data) {
    std::ostringstream ss;
    ss << std::hex << std::setfill('0');
    for (unsigned char c : data) {
        ss << std::setw(2) << static_cast<int>(c);
    }
    return ss.str();
}

std::string Crypto::hex_decode(const std::string& hex) {
    std::string result;
    for (size_t i = 0; i < hex.length(); i += 2) {
        std::string byte = hex.substr(i, 2);
        char chr = static_cast<char>(strtol(byte.c_str(), nullptr, 16));
        result.push_back(chr);
    }
    return result;
}

// ===== RANDOM =====

std::string Crypto::random_bytes(int length) {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_int_distribution<> dis(0, 255);

    std::string result;
    result.reserve(length);
    for (int i = 0; i < length; ++i) {
        result += static_cast<char>(dis(gen));
    }
    return result;
}

std::string Crypto::random_hex(int length) {
    return hex_encode(random_bytes(length));
}

int Crypto::random_int(int min, int max) {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(min, max);
    return dis(gen);
}

// ===== UTILITY =====

bool Crypto::constant_time_compare(const std::string& a, const std::string& b) {
    if (a.length() != b.length()) {
        return false;
    }

    volatile unsigned char result = 0;
    for (size_t i = 0; i < a.length(); ++i) {
        result |= a[i] ^ b[i];
    }
    return result == 0;
}

// ===== FREE FUNCTIONS =====

std::string hash_password(const std::string& password) {
    // Simple password hashing (use proper library in production!)
    std::string salt = Crypto::random_hex(16);
    std::string hash = Crypto::sha256(password + salt);
    return salt + ":" + hash;
}

bool verify_password(const std::string& password, const std::string& hash) {
    size_t colon_pos = hash.find(':');
    if (colon_pos == std::string::npos) {
        return false;
    }

    std::string salt = hash.substr(0, colon_pos);
    std::string stored_hash = hash.substr(colon_pos + 1);
    std::string computed_hash = Crypto::sha256(password + salt);

    return Crypto::constant_time_compare(computed_hash, stored_hash);
}

} // namespace inbuilds
} // namespace includecpp
