#include "validation.hpp"
#include <algorithm>
#include <cctype>

namespace simple_object_storage {

// Define regex patterns
// Labels and tags: alphanumeric, dash, underscore, dot
// Label:tag format: label:tag where both parts follow the pattern
const std::regex InputValidator::LABEL_TAG_PATTERN("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}:[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$");
const std::regex InputValidator::LABEL_PATTERN("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$");
const std::regex InputValidator::TAG_PATTERN("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$");
const std::regex InputValidator::HASH_PATTERN("^[a-f0-9]{64}$");  // SHA-256 lowercase hex
const std::regex InputValidator::JSON_FIELD_PATTERN("^[a-zA-Z_][a-zA-Z0-9_]{0,127}$");

InputValidator::ValidationResult InputValidator::validateLabelTag(const std::string& labeltag) {
    // Check for empty string
    if (labeltag.empty()) {
        return ValidationResult(false, "Label:tag cannot be empty");
    }
    
    // Check maximum length
    if (labeltag.length() > MAX_LABELTAG_LENGTH) {
        return ValidationResult(false, "Label:tag exceeds maximum length of " + 
                              std::to_string(MAX_LABELTAG_LENGTH) + " characters");
    }
    
    // Check for colon separator
    size_t colonPos = labeltag.find(':');
    if (colonPos == std::string::npos) {
        return ValidationResult(false, "Label:tag must contain a colon separator");
    }
    
    // Check that there's exactly one colon
    if (labeltag.find(':', colonPos + 1) != std::string::npos) {
        return ValidationResult(false, "Label:tag must contain exactly one colon separator");
    }
    
    // Extract label and tag
    std::string label = labeltag.substr(0, colonPos);
    std::string tag = labeltag.substr(colonPos + 1);
    
    // Validate label
    auto labelResult = validateLabel(label);
    if (!labelResult.valid) {
        return ValidationResult(false, "Invalid label: " + labelResult.error);
    }
    
    // Validate tag
    auto tagResult = validateTag(tag);
    if (!tagResult.valid) {
        return ValidationResult(false, "Invalid tag: " + tagResult.error);
    }
    
    // Check against regex pattern for additional validation
    if (!std::regex_match(labeltag, LABEL_TAG_PATTERN)) {
        return ValidationResult(false, "Label:tag contains invalid characters or format");
    }
    
    return ValidationResult(true);
}

InputValidator::ValidationResult InputValidator::validateLabel(const std::string& label) {
    if (label.empty()) {
        return ValidationResult(false, "Label cannot be empty");
    }
    
    if (label.length() > MAX_LABEL_LENGTH) {
        return ValidationResult(false, "Label exceeds maximum length of " + 
                              std::to_string(MAX_LABEL_LENGTH) + " characters");
    }
    
    // Must start with alphanumeric
    if (!std::isalnum(static_cast<unsigned char>(label[0]))) {
        return ValidationResult(false, "Label must start with an alphanumeric character");
    }
    
    // Check against pattern
    if (!std::regex_match(label, LABEL_PATTERN)) {
        return ValidationResult(false, "Label contains invalid characters. Only alphanumeric, dash, underscore, and dot allowed");
    }
    
    return ValidationResult(true);
}

InputValidator::ValidationResult InputValidator::validateTag(const std::string& tag) {
    if (tag.empty()) {
        return ValidationResult(false, "Tag cannot be empty");
    }
    
    if (tag.length() > MAX_TAG_LENGTH) {
        return ValidationResult(false, "Tag exceeds maximum length of " + 
                              std::to_string(MAX_TAG_LENGTH) + " characters");
    }
    
    // Must start with alphanumeric
    if (!std::isalnum(static_cast<unsigned char>(tag[0]))) {
        return ValidationResult(false, "Tag must start with an alphanumeric character");
    }
    
    // Check against pattern
    if (!std::regex_match(tag, TAG_PATTERN)) {
        return ValidationResult(false, "Tag contains invalid characters. Only alphanumeric, dash, underscore, and dot allowed");
    }
    
    return ValidationResult(true);
}

InputValidator::ValidationResult InputValidator::validateLabelTags(const std::vector<std::string>& labeltags) {
    if (labeltags.empty()) {
        return ValidationResult(false, "At least one label:tag is required");
    }
    
    if (labeltags.size() > MAX_LABELTAGS_COUNT) {
        return ValidationResult(false, "Too many label:tags. Maximum allowed is " + 
                              std::to_string(MAX_LABELTAGS_COUNT));
    }
    
    // Check for duplicates
    std::vector<std::string> sorted = labeltags;
    std::sort(sorted.begin(), sorted.end());
    if (std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end()) {
        return ValidationResult(false, "Duplicate label:tag entries found");
    }
    
    // Validate each label:tag
    for (size_t i = 0; i < labeltags.size(); ++i) {
        auto result = validateLabelTag(labeltags[i]);
        if (!result.valid) {
            return ValidationResult(false, "Invalid label:tag at index " + 
                                  std::to_string(i) + ": " + result.error);
        }
    }
    
    return ValidationResult(true);
}

InputValidator::ValidationResult InputValidator::validateHash(const std::string& hash) {
    if (hash.empty()) {
        return ValidationResult(false, "Hash cannot be empty");
    }
    
    if (hash.length() != MAX_HASH_LENGTH) {
        return ValidationResult(false, "Hash must be exactly " + 
                              std::to_string(MAX_HASH_LENGTH) + " characters (SHA-256)");
    }
    
    // Convert to lowercase for comparison
    std::string lowerHash = hash;
    std::transform(lowerHash.begin(), lowerHash.end(), lowerHash.begin(), ::tolower);
    
    if (!std::regex_match(lowerHash, HASH_PATTERN)) {
        return ValidationResult(false, "Hash must be a valid SHA-256 hex string");
    }
    
    return ValidationResult(true);
}

InputValidator::ValidationResult InputValidator::validateMetadata(const nlohmann::json& metadata) {
    // Check if metadata is an object
    if (!metadata.is_object()) {
        return ValidationResult(false, "Metadata must be a JSON object");
    }
    
    // Check size
    std::string metadataStr = metadata.dump();
    if (metadataStr.length() > MAX_METADATA_SIZE) {
        return ValidationResult(false, "Metadata exceeds maximum size of " + 
                              std::to_string(MAX_METADATA_SIZE) + " bytes");
    }
    
    // Validate each field
    for (auto& [key, value] : metadata.items()) {
        // Validate field name
        if (!isValidJsonFieldName(key)) {
            return ValidationResult(false, "Invalid metadata field name: " + key + 
                                  ". Field names must start with a letter or underscore and contain only alphanumeric characters and underscores");
        }
        
        // Check field name length
        if (key.length() > 128) {
            return ValidationResult(false, "Metadata field name too long: " + key);
        }
        
        // Check value type and size
        if (value.is_string()) {
            if (value.get<std::string>().length() > MAX_JSON_FIELD_LENGTH) {
                return ValidationResult(false, "Metadata field '" + key + 
                                      "' value exceeds maximum length of " + 
                                      std::to_string(MAX_JSON_FIELD_LENGTH));
            }
        } else if (value.is_array()) {
            // Limit array size
            if (value.size() > 1000) {
                return ValidationResult(false, "Metadata field '" + key + 
                                      "' array exceeds maximum size of 1000 elements");
            }
            // Check each array element if it's a string
            for (const auto& elem : value) {
                if (elem.is_string() && elem.get<std::string>().length() > MAX_JSON_FIELD_LENGTH) {
                    return ValidationResult(false, "Metadata field '" + key + 
                                          "' array element exceeds maximum string length");
                }
            }
        } else if (value.is_object()) {
            // Recursively validate nested objects (with depth limit)
            static int depth = 0;
            if (++depth > 5) {
                --depth;
                return ValidationResult(false, "Metadata nesting depth exceeds maximum of 5 levels");
            }
            auto result = validateMetadata(value);
            --depth;
            if (!result.valid) {
                return ValidationResult(false, "Invalid nested metadata in field '" + key + "': " + result.error);
            }
        }
    }
    
    return ValidationResult(true);
}

InputValidator::ValidationResult InputValidator::validateFilename(const std::string& filename) {
    if (filename.empty()) {
        return ValidationResult(false, "Filename cannot be empty");
    }
    
    if (filename.length() > MAX_FILENAME_LENGTH) {
        return ValidationResult(false, "Filename exceeds maximum length of " + 
                              std::to_string(MAX_FILENAME_LENGTH) + " characters");
    }
    
    // Check for directory traversal attempts
    if (filename.find("..") != std::string::npos) {
        return ValidationResult(false, "Filename cannot contain '..' (directory traversal)");
    }
    
    if (filename.find("/") != std::string::npos || filename.find("\\") != std::string::npos) {
        return ValidationResult(false, "Filename cannot contain path separators");
    }
    
    // Check for null bytes
    if (filename.find('\0') != std::string::npos) {
        return ValidationResult(false, "Filename cannot contain null bytes");
    }
    
    // Check for control characters
    for (char c : filename) {
        if (std::iscntrl(static_cast<unsigned char>(c))) {
            return ValidationResult(false, "Filename cannot contain control characters");
        }
    }
    
    // Disallow certain problematic filenames
    std::string lowerFilename = filename;
    std::transform(lowerFilename.begin(), lowerFilename.end(), lowerFilename.begin(), ::tolower);
    
    // Windows reserved names
    const std::vector<std::string> reserved = {
        "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", 
        "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", 
        "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
    };
    
    for (const auto& r : reserved) {
        if (lowerFilename == r || lowerFilename.find(r + ".") == 0) {
            return ValidationResult(false, "Filename uses a reserved system name");
        }
    }
    
    return ValidationResult(true);
}

bool InputValidator::isValidLabelTagCharset(const std::string& str) {
    for (char c : str) {
        if (!std::isalnum(static_cast<unsigned char>(c)) && 
            c != '-' && c != '_' && c != '.' && c != ':') {
            return false;
        }
    }
    return true;
}

bool InputValidator::isValidJsonFieldName(const std::string& fieldName) {
    if (fieldName.empty()) return false;
    
    // Must start with letter or underscore
    if (!std::isalpha(static_cast<unsigned char>(fieldName[0])) && fieldName[0] != '_') {
        return false;
    }
    
    // Rest can be alphanumeric or underscore
    for (size_t i = 1; i < fieldName.length(); ++i) {
        char c = fieldName[i];
        if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
            return false;
        }
    }
    
    return true;
}

} // namespace simple_object_storage