// Simplified BCrypt implementation for token hashing
// Based on OpenBSD's bcrypt with modifications for C++ header-only use
// This implementation uses SHA-256 as the core hashing function with salt and multiple rounds

#ifndef BCRYPT_HPP
#define BCRYPT_HPP

#include <string>
#include <vector>
#include <random>
#include <sstream>
#include <iomanip>
#include <cstring>
#include <openssl/sha.h>
#include <openssl/rand.h>

namespace simple_object_storage {

class BCrypt {
public:
    // Default cost factor (number of rounds = 2^cost)
    static constexpr int DEFAULT_COST = 12;
    static constexpr int MIN_COST = 4;
    static constexpr int MAX_COST = 31;
    static constexpr size_t SALT_LENGTH = 16; // 128 bits
    static constexpr size_t HASH_LENGTH = 32; // 256 bits (SHA-256)

    // Generate a hash from a password/token
    static std::string hashPassword(const std::string& password, int cost = DEFAULT_COST) {
        if (cost < MIN_COST || cost > MAX_COST) {
            cost = DEFAULT_COST;
        }

        // Generate random salt
        std::vector<unsigned char> salt(SALT_LENGTH);
        if (RAND_bytes(salt.data(), SALT_LENGTH) != 1) {
            throw std::runtime_error("Failed to generate random salt");
        }

        // Perform the hashing
        std::vector<unsigned char> hash = hashWithSalt(password, salt, cost);

        // Format: $2b$<cost>$<salt><hash>
        // We use a simplified format for header-only implementation
        return formatHash(cost, salt, hash);
    }

    // Verify a password against a hash
    static bool verifyPassword(const std::string& password, const std::string& hash) {
        // Parse the hash to extract cost, salt, and expected hash
        int cost;
        std::vector<unsigned char> salt;
        std::vector<unsigned char> expectedHash;
        
        if (!parseHash(hash, cost, salt, expectedHash)) {
            return false;
        }

        // Hash the password with the extracted salt and cost
        std::vector<unsigned char> computedHash = hashWithSalt(password, salt, cost);

        // Constant-time comparison to prevent timing attacks
        return constantTimeCompare(computedHash, expectedHash);
    }

private:
    // Perform the actual hashing with salt and cost
    static std::vector<unsigned char> hashWithSalt(const std::string& password, 
                                                    const std::vector<unsigned char>& salt, 
                                                    int cost) {
        // Number of iterations = 2^cost
        size_t rounds = 1ULL << cost;
        
        // Initial hash: SHA256(salt + password)
        std::vector<unsigned char> data;
        data.insert(data.end(), salt.begin(), salt.end());
        data.insert(data.end(), password.begin(), password.end());
        
        std::vector<unsigned char> hash(SHA256_DIGEST_LENGTH);
        SHA256(data.data(), data.size(), hash.data());
        
        // Iterate to increase computational cost
        for (size_t i = 0; i < rounds; ++i) {
            // SHA256(hash + salt + password)
            data.clear();
            data.insert(data.end(), hash.begin(), hash.end());
            data.insert(data.end(), salt.begin(), salt.end());
            data.insert(data.end(), password.begin(), password.end());
            SHA256(data.data(), data.size(), hash.data());
        }
        
        return hash;
    }

    // Format hash for storage
    static std::string formatHash(int cost, const std::vector<unsigned char>& salt, 
                                  const std::vector<unsigned char>& hash) {
        std::stringstream ss;
        ss << "$2b$" << std::setfill('0') << std::setw(2) << cost << "$";
        
        // Encode salt as hex
        for (unsigned char byte : salt) {
            ss << std::hex << std::setw(2) << static_cast<int>(byte);
        }
        
        // Encode hash as hex
        for (unsigned char byte : hash) {
            ss << std::hex << std::setw(2) << static_cast<int>(byte);
        }
        
        return ss.str();
    }

    // Parse a formatted hash
    static bool parseHash(const std::string& hashStr, int& cost, 
                          std::vector<unsigned char>& salt, 
                          std::vector<unsigned char>& hash) {
        // Expected format: $2b$<cost>$<salt><hash>
        if (hashStr.size() < 10 || hashStr.substr(0, 4) != "$2b$") {
            return false;
        }
        
        // Find the second $
        size_t secondDollar = hashStr.find('$', 4);
        if (secondDollar == std::string::npos) {
            return false;
        }
        
        // Parse cost
        try {
            cost = std::stoi(hashStr.substr(4, secondDollar - 4));
        } catch (...) {
            return false;
        }
        
        // Parse salt and hash (both in hex)
        std::string hexData = hashStr.substr(secondDollar + 1);
        if (hexData.size() != (SALT_LENGTH + HASH_LENGTH) * 2) {
            return false;
        }
        
        // Decode salt
        salt.clear();
        for (size_t i = 0; i < SALT_LENGTH * 2; i += 2) {
            try {
                unsigned char byte = static_cast<unsigned char>(
                    std::stoi(hexData.substr(i, 2), nullptr, 16));
                salt.push_back(byte);
            } catch (...) {
                return false;
            }
        }
        
        // Decode hash
        hash.clear();
        for (size_t i = SALT_LENGTH * 2; i < hexData.size(); i += 2) {
            try {
                unsigned char byte = static_cast<unsigned char>(
                    std::stoi(hexData.substr(i, 2), nullptr, 16));
                hash.push_back(byte);
            } catch (...) {
                return false;
            }
        }
        
        return true;
    }

    // Constant-time comparison to prevent timing attacks
    static bool constantTimeCompare(const std::vector<unsigned char>& a, 
                                    const std::vector<unsigned char>& b) {
        if (a.size() != b.size()) {
            return false;
        }
        
        unsigned char result = 0;
        for (size_t i = 0; i < a.size(); ++i) {
            result |= a[i] ^ b[i];
        }
        
        return result == 0;
    }
};

} // namespace simple_object_storage

#endif // BCRYPT_HPP