#include "chat_fixture_tooling.h"

#include <nlohmann/json.hpp>
#include <sodium.h>

#include <algorithm>
#include <cctype>
#include <cstring>
#include <fstream>
#include <optional>
#include <sstream>
#include <utility>

namespace dragonx::chat {
namespace {

bool isHexString(const std::string& value)
{
    for (unsigned char ch : value) {
        if (!std::isxdigit(ch)) return false;
    }
    return true;
}

std::optional<unsigned char> hexNibble(unsigned char character)
{
    if (character >= '0' && character <= '9') return static_cast<unsigned char>(character - '0');
    if (character >= 'a' && character <= 'f') return static_cast<unsigned char>(character - 'a' + 10);
    if (character >= 'A' && character <= 'F') return static_cast<unsigned char>(character - 'A' + 10);
    return std::nullopt;
}

std::optional<std::string> readJsonString(const nlohmann::json& object, const char* key)
{
    auto it = object.find(key);
    if (it == object.end() || !it->is_string()) return std::nullopt;
    return it->get<std::string>();
}

std::optional<std::size_t> readJsonSize(const nlohmann::json& object, const char* key)
{
    auto it = object.find(key);
    if (it == object.end() || (!it->is_number_integer() && !it->is_number_unsigned())) return std::nullopt;
    if (it->is_number_unsigned()) return it->get<std::size_t>();
    auto value = it->get<long long>();
    if (value < 0) return std::nullopt;
    return static_cast<std::size_t>(value);
}

std::optional<HushChatDecryptDirection> parseFixtureDirection(const std::string& value)
{
    if (value == "Incoming") return HushChatDecryptDirection::Incoming;
    if (value == "Outgoing") return HushChatDecryptDirection::Outgoing;
    return std::nullopt;
}

std::optional<HushChatSessionKeySelection> parseFixtureSessionKeySelection(const std::string& value)
{
    if (value == "ClientRx") return HushChatSessionKeySelection::ClientRx;
    if (value == "ServerTx") return HushChatSessionKeySelection::ServerTx;
    return std::nullopt;
}

std::optional<HushChatCompatibilityFixtureKind> parseFixtureKind(const std::string& value)
{
    if (value == "incoming_memo") return HushChatCompatibilityFixtureKind::IncomingMemo;
    if (value == "outgoing_memo") return HushChatCompatibilityFixtureKind::OutgoingMemo;
    if (value == "seed_public_key_projection") return HushChatCompatibilityFixtureKind::SeedPublicKeyProjection;
    if (value == "corrupted_auth_failure") return HushChatCompatibilityFixtureKind::CorruptedAuthFailure;
    if (value == "cont_exclusion") return HushChatCompatibilityFixtureKind::ContactExclusion;
    return std::nullopt;
}

std::optional<HushChatCompatibilityFixtureFileStatus> parseFixtureFileStatus(const std::string& value)
{
    if (value == "pending") return HushChatCompatibilityFixtureFileStatus::Pending;
    if (value == "ready") return HushChatCompatibilityFixtureFileStatus::Ready;
    return std::nullopt;
}

std::string lowerCopy(const std::string& value)
{
    std::string lowered;
    lowered.reserve(value.size());
    for (unsigned char ch : value) lowered.push_back(static_cast<char>(std::tolower(ch)));
    return lowered;
}

bool isProhibitedCaptureManifestKey(const std::string& key)
{
    const std::string lowered = lowerCopy(key);
    if (lowered.rfind("no_", 0) == 0) return false;

    static const std::vector<std::string> prohibitedKeys = {
        "fixture",
        "fixtures",
        "passphrase",
        "password",
        "plaintext",
        "plaintext_hash_hex",
        "memo",
        "memostr",
        "memo_contents",
        "header_memo",
        "ciphertext",
        "ciphertext_memo",
        "ciphertext_bytes",
        "private_key",
        "viewing_key",
        "spending_key",
        "wallet_seed",
        "wallet_file",
        "wallet_files",
        "stored_chat_key_hex",
        "local_public_key_hex",
        "peer_public_key_hex",
        "public_key_hex",
        "secretstream_header",
        "derived_key",
        "session_key",
        "secret_key",
        "seed_bytes"
    };

    return std::find(prohibitedKeys.begin(), prohibitedKeys.end(), lowered) != prohibitedKeys.end();
}

std::size_t countProhibitedCaptureManifestFields(const nlohmann::json& value)
{
    std::size_t count = 0;
    if (value.is_object()) {
        for (auto it = value.begin(); it != value.end(); ++it) {
            if (isProhibitedCaptureManifestKey(it.key())) ++count;
            count += countProhibitedCaptureManifestFields(it.value());
        }
    } else if (value.is_array()) {
        for (const auto& item : value) count += countProhibitedCaptureManifestFields(item);
    }
    return count;
}

std::vector<const char*> requiredCaptureHandlingFlags()
{
    return {
        "disposable_wallets_only",
        "non_sensitive_vectors_only",
        "no_passphrases",
        "no_plaintext",
        "no_memo_contents",
        "no_private_keys",
        "no_wallet_files",
        "no_ciphertext_byte_dumps",
        "no_derived_keys",
        "no_session_keys",
        "redacted_report_only"
    };
}

} // namespace

const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error)
{
    switch (error) {
    case HushChatDecryptPreflightError::None:
        return "None";
    case HushChatDecryptPreflightError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatDecryptPreflightError::NonMessageHeader:
        return "NonMessageHeader";
    case HushChatDecryptPreflightError::InvalidHeaderNumber:
        return "InvalidHeaderNumber";
    case HushChatDecryptPreflightError::UnsupportedVersion:
        return "UnsupportedVersion";
    case HushChatDecryptPreflightError::MissingReplyAddress:
        return "MissingReplyAddress";
    case HushChatDecryptPreflightError::MissingConversationId:
        return "MissingConversationId";
    case HushChatDecryptPreflightError::InvalidSecretstreamHeader:
        return "InvalidSecretstreamHeader";
    case HushChatDecryptPreflightError::InvalidPublicKey:
        return "InvalidPublicKey";
    case HushChatDecryptPreflightError::EmptyCiphertext:
        return "EmptyCiphertext";
    case HushChatDecryptPreflightError::OversizedCiphertext:
        return "OversizedCiphertext";
    case HushChatDecryptPreflightError::OddLengthCiphertext:
        return "OddLengthCiphertext";
    case HushChatDecryptPreflightError::InvalidCiphertextHex:
        return "InvalidCiphertextHex";
    case HushChatDecryptPreflightError::TruncatedCiphertext:
        return "TruncatedCiphertext";
    }
    return "Unknown";
}

const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error)
{
    switch (error) {
    case HushChatHexDecodeError::None:
        return "None";
    case HushChatHexDecodeError::Empty:
        return "Empty";
    case HushChatHexDecodeError::OddLength:
        return "OddLength";
    case HushChatHexDecodeError::InvalidHex:
        return "InvalidHex";
    case HushChatHexDecodeError::UnexpectedByteLength:
        return "UnexpectedByteLength";
    }
    return "Unknown";
}

const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction)
{
    switch (direction) {
    case HushChatDecryptDirection::Incoming:
        return "Incoming";
    case HushChatDecryptDirection::Outgoing:
        return "Outgoing";
    }
    return "Unknown";
}

const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection)
{
    switch (selection) {
    case HushChatSessionKeySelection::ClientRx:
        return "ClientRx";
    case HushChatSessionKeySelection::ServerTx:
        return "ServerTx";
    }
    return "Unknown";
}

const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error)
{
    switch (error) {
    case HushChatDecryptInputError::None:
        return "None";
    case HushChatDecryptInputError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatDecryptInputError::InvalidStoredChatKey:
        return "InvalidStoredChatKey";
    case HushChatDecryptInputError::DecryptPreflightFailed:
        return "DecryptPreflightFailed";
    case HushChatDecryptInputError::InvalidPeerPublicKey:
        return "InvalidPeerPublicKey";
    case HushChatDecryptInputError::InvalidStreamHeader:
        return "InvalidStreamHeader";
    case HushChatDecryptInputError::InvalidCiphertext:
        return "InvalidCiphertext";
    }
    return "Unknown";
}

const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error)
{
    switch (error) {
    case HushChatCompatibilityFixtureError::None:
        return "None";
    case HushChatCompatibilityFixtureError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatCompatibilityFixtureError::MissingFixtureId:
        return "MissingFixtureId";
    case HushChatCompatibilityFixtureError::InvalidLocalPublicKey:
        return "InvalidLocalPublicKey";
    case HushChatCompatibilityFixtureError::InvalidPeerPublicKey:
        return "InvalidPeerPublicKey";
    case HushChatCompatibilityFixtureError::InvalidHeaderMemo:
        return "InvalidHeaderMemo";
    case HushChatCompatibilityFixtureError::InvalidMemoPair:
        return "InvalidMemoPair";
    case HushChatCompatibilityFixtureError::NonMemoHeader:
        return "NonMemoHeader";
    case HushChatCompatibilityFixtureError::HeaderPublicKeyMismatch:
        return "HeaderPublicKeyMismatch";
    case HushChatCompatibilityFixtureError::DecryptInputFailed:
        return "DecryptInputFailed";
    case HushChatCompatibilityFixtureError::NotFixtureReady:
        return "NotFixtureReady";
    case HushChatCompatibilityFixtureError::ExpectedStoredChatKeyLengthMismatch:
        return "ExpectedStoredChatKeyLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedSeedLengthMismatch:
        return "ExpectedSeedLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedLocalPublicKeyLengthMismatch:
        return "ExpectedLocalPublicKeyLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedPeerPublicKeyLengthMismatch:
        return "ExpectedPeerPublicKeyLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedStreamHeaderLengthMismatch:
        return "ExpectedStreamHeaderLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedCiphertextLengthMismatch:
        return "ExpectedCiphertextLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedPlaintextLengthMismatch:
        return "ExpectedPlaintextLengthMismatch";
    case HushChatCompatibilityFixtureError::ExpectedRoleMismatch:
        return "ExpectedRoleMismatch";
    case HushChatCompatibilityFixtureError::InvalidPlaintextHash:
        return "InvalidPlaintextHash";
    }
    return "Unknown";
}

const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind)
{
    switch (kind) {
    case HushChatCompatibilityFixtureKind::IncomingMemo:
        return "incoming_memo";
    case HushChatCompatibilityFixtureKind::OutgoingMemo:
        return "outgoing_memo";
    case HushChatCompatibilityFixtureKind::SeedPublicKeyProjection:
        return "seed_public_key_projection";
    case HushChatCompatibilityFixtureKind::CorruptedAuthFailure:
        return "corrupted_auth_failure";
    case HushChatCompatibilityFixtureKind::ContactExclusion:
        return "cont_exclusion";
    }
    return "unknown";
}

const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status)
{
    switch (status) {
    case HushChatCompatibilityFixtureFileStatus::Pending:
        return "pending";
    case HushChatCompatibilityFixtureFileStatus::Ready:
        return "ready";
    }
    return "unknown";
}

const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error)
{
    switch (error) {
    case HushChatCompatibilityFixtureFileError::None:
        return "None";
    case HushChatCompatibilityFixtureFileError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatCompatibilityFixtureFileError::InvalidJson:
        return "InvalidJson";
    case HushChatCompatibilityFixtureFileError::JsonNotObject:
        return "JsonNotObject";
    case HushChatCompatibilityFixtureFileError::InvalidSchema:
        return "InvalidSchema";
    case HushChatCompatibilityFixtureFileError::MissingKind:
        return "MissingKind";
    case HushChatCompatibilityFixtureFileError::UnknownKind:
        return "UnknownKind";
    case HushChatCompatibilityFixtureFileError::MissingStatus:
        return "MissingStatus";
    case HushChatCompatibilityFixtureFileError::UnknownStatus:
        return "UnknownStatus";
    case HushChatCompatibilityFixtureFileError::MissingFixtureId:
        return "MissingFixtureId";
    case HushChatCompatibilityFixtureFileError::MissingPendingReason:
        return "MissingPendingReason";
    case HushChatCompatibilityFixtureFileError::MissingFixtureObject:
        return "MissingFixtureObject";
    case HushChatCompatibilityFixtureFileError::InvalidFixtureField:
        return "InvalidFixtureField";
    case HushChatCompatibilityFixtureFileError::FixtureVerificationFailed:
        return "FixtureVerificationFailed";
    case HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded:
        return "ContactFixtureNotExcluded";
    case HushChatCompatibilityFixtureFileError::FileReadFailed:
        return "FileReadFailed";
    }
    return "Unknown";
}

const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error)
{
    switch (error) {
    case HushChatSeedPublicKeyProjectionError::None:
        return "None";
    case HushChatSeedPublicKeyProjectionError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatSeedPublicKeyProjectionError::MissingFixtureId:
        return "MissingFixtureId";
    case HushChatSeedPublicKeyProjectionError::InvalidStoredChatKey:
        return "InvalidStoredChatKey";
    case HushChatSeedPublicKeyProjectionError::InvalidLocalPublicKey:
        return "InvalidLocalPublicKey";
    case HushChatSeedPublicKeyProjectionError::ExpectedStoredChatKeyLengthMismatch:
        return "ExpectedStoredChatKeyLengthMismatch";
    case HushChatSeedPublicKeyProjectionError::ExpectedSeedLengthMismatch:
        return "ExpectedSeedLengthMismatch";
    case HushChatSeedPublicKeyProjectionError::ExpectedLocalPublicKeyLengthMismatch:
        return "ExpectedLocalPublicKeyLengthMismatch";
    case HushChatSeedPublicKeyProjectionError::SodiumInitializationFailed:
        return "SodiumInitializationFailed";
    case HushChatSeedPublicKeyProjectionError::KeypairProjectionFailed:
        return "KeypairProjectionFailed";
    case HushChatSeedPublicKeyProjectionError::ProjectedPublicKeyMismatch:
        return "ProjectedPublicKeyMismatch";
    }
    return "Unknown";
}

const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error)
{
    switch (error) {
    case HushChatCorruptedAuthFailureReadinessError::None:
        return "None";
    case HushChatCorruptedAuthFailureReadinessError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatCorruptedAuthFailureReadinessError::FixturePending:
        return "FixturePending";
    case HushChatCorruptedAuthFailureReadinessError::WrongFixtureKind:
        return "WrongFixtureKind";
    case HushChatCorruptedAuthFailureReadinessError::FixtureNotVerified:
        return "FixtureNotVerified";
    case HushChatCorruptedAuthFailureReadinessError::SeedProjectionNotVerified:
        return "SeedProjectionNotVerified";
    }
    return "Unknown";
}

const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error)
{
    switch (error) {
    case HushChatCompatibilityFixtureImportError::None:
        return "None";
    case HushChatCompatibilityFixtureImportError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatCompatibilityFixtureImportError::MissingRequiredKind:
        return "MissingRequiredKind";
    case HushChatCompatibilityFixtureImportError::DuplicateKind:
        return "DuplicateKind";
    case HushChatCompatibilityFixtureImportError::FixtureLoadFailed:
        return "FixtureLoadFailed";
    case HushChatCompatibilityFixtureImportError::FixtureKindMismatch:
        return "FixtureKindMismatch";
    case HushChatCompatibilityFixtureImportError::FixturePending:
        return "FixturePending";
    case HushChatCompatibilityFixtureImportError::FixtureInvalid:
        return "FixtureInvalid";
    case HushChatCompatibilityFixtureImportError::FixtureNotVerified:
        return "FixtureNotVerified";
    case HushChatCompatibilityFixtureImportError::SeedProjectionFailed:
        return "SeedProjectionFailed";
    case HushChatCompatibilityFixtureImportError::AuthFailureScaffoldFailed:
        return "AuthFailureScaffoldFailed";
    case HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded:
        return "ContactFixtureNotExcluded";
    }
    return "Unknown";
}

const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error)
{
    switch (error) {
    case HushChatCaptureManifestError::None:
        return "None";
    case HushChatCaptureManifestError::FeatureDisabled:
        return "FeatureDisabled";
    case HushChatCaptureManifestError::FileReadFailed:
        return "FileReadFailed";
    case HushChatCaptureManifestError::InvalidJson:
        return "InvalidJson";
    case HushChatCaptureManifestError::JsonNotObject:
        return "JsonNotObject";
    case HushChatCaptureManifestError::InvalidSchema:
        return "InvalidSchema";
    case HushChatCaptureManifestError::MissingManifestId:
        return "MissingManifestId";
    case HushChatCaptureManifestError::MissingStatus:
        return "MissingStatus";
    case HushChatCaptureManifestError::UnknownStatus:
        return "UnknownStatus";
    case HushChatCaptureManifestError::MissingFixtureDirectory:
        return "MissingFixtureDirectory";
    case HushChatCaptureManifestError::MissingDryRunCommand:
        return "MissingDryRunCommand";
    case HushChatCaptureManifestError::InvalidDryRunCommand:
        return "InvalidDryRunCommand";
    case HushChatCaptureManifestError::MissingProvenance:
        return "MissingProvenance";
    case HushChatCaptureManifestError::MissingSourceClient:
        return "MissingSourceClient";
    case HushChatCaptureManifestError::InvalidSourceClient:
        return "InvalidSourceClient";
    case HushChatCaptureManifestError::MissingSourceClientVersion:
        return "MissingSourceClientVersion";
    case HushChatCaptureManifestError::MissingCaptureDate:
        return "MissingCaptureDate";
    case HushChatCaptureManifestError::MissingNetwork:
        return "MissingNetwork";
    case HushChatCaptureManifestError::MissingCaptureMethod:
        return "MissingCaptureMethod";
    case HushChatCaptureManifestError::MissingHandling:
        return "MissingHandling";
    case HushChatCaptureManifestError::MissingHandlingFlag:
        return "MissingHandlingFlag";
    case HushChatCaptureManifestError::HandlingFlagNotTrue:
        return "HandlingFlagNotTrue";
    case HushChatCaptureManifestError::MissingCategories:
        return "MissingCategories";
    case HushChatCaptureManifestError::InvalidCategoryEntry:
        return "InvalidCategoryEntry";
    case HushChatCaptureManifestError::UnknownCategory:
        return "UnknownCategory";
    case HushChatCaptureManifestError::DuplicateCategory:
        return "DuplicateCategory";
    case HushChatCaptureManifestError::MissingRequiredCategory:
        return "MissingRequiredCategory";
    case HushChatCaptureManifestError::ProhibitedFieldPresent:
        return "ProhibitedFieldPresent";
    }
    return "Unknown";
}

HushChatSessionKeySelection hushChatSessionKeySelectionForDirection(HushChatDecryptDirection direction)
{
    switch (direction) {
    case HushChatDecryptDirection::Incoming:
        return HushChatSessionKeySelection::ClientRx;
    case HushChatDecryptDirection::Outgoing:
        return HushChatSessionKeySelection::ServerTx;
    }
    return HushChatSessionKeySelection::ClientRx;
}

HushChatDecryptPreflightResult validateHushChatMemoDecryptPreflight(
    const HushChatDecryptPreflightInput& input,
    bool featureEnabled)
{
    auto failPreflight = [&](HushChatDecryptPreflightError error) {
        HushChatDecryptPreflightResult result;
        result.feature_enabled = featureEnabled;
        result.error = error;
        result.error_name = hushChatDecryptPreflightErrorName(error);
        return result;
    };

    if (!featureEnabled) return failPreflight(HushChatDecryptPreflightError::FeatureDisabled);

    const auto& header = input.header;
    if (header.type != HushChatHeaderType::Message) {
        return failPreflight(HushChatDecryptPreflightError::NonMessageHeader);
    }
    if (header.header_number < 1) return failPreflight(HushChatDecryptPreflightError::InvalidHeaderNumber);
    if (header.version != kHushChatSupportedVersion) {
        return failPreflight(HushChatDecryptPreflightError::UnsupportedVersion);
    }
    if (header.reply_zaddr.empty()) return failPreflight(HushChatDecryptPreflightError::MissingReplyAddress);
    if (header.conversation_id.empty()) return failPreflight(HushChatDecryptPreflightError::MissingConversationId);
    if (header.secretstream_header_hex.size() != kHushChatSecretstreamHeaderHexLength ||
        !isHexString(header.secretstream_header_hex)) {
        return failPreflight(HushChatDecryptPreflightError::InvalidSecretstreamHeader);
    }
    if (header.public_key_hex.size() != kHushChatPublicKeyHexLength || !isHexString(header.public_key_hex)) {
        return failPreflight(HushChatDecryptPreflightError::InvalidPublicKey);
    }

    if (input.ciphertext_hex.empty()) return failPreflight(HushChatDecryptPreflightError::EmptyCiphertext);
    if (input.ciphertext_hex.size() > kHushChatMemoByteLimit) {
        return failPreflight(HushChatDecryptPreflightError::OversizedCiphertext);
    }
    if (input.ciphertext_hex.size() % 2 != 0) return failPreflight(HushChatDecryptPreflightError::OddLengthCiphertext);
    if (!isHexString(input.ciphertext_hex)) return failPreflight(HushChatDecryptPreflightError::InvalidCiphertextHex);

    const std::size_t ciphertextSize = input.ciphertext_hex.size() / 2;
    if (ciphertextSize <= kHushChatSecretstreamABytes) {
        return failPreflight(HushChatDecryptPreflightError::TruncatedCiphertext);
    }

    HushChatDecryptPreflightResult result;
    result.ok = true;
    result.feature_enabled = true;
    result.error = HushChatDecryptPreflightError::None;
    result.error_name = hushChatDecryptPreflightErrorName(result.error);
    result.ciphertext_size = ciphertextSize;
    return result;
}

HushChatHexDecodeResult decodeHushChatHexBytes(const std::string& hex,
                                               std::size_t expectedByteLength)
{
    auto failDecode = [](HushChatHexDecodeError error) {
        HushChatHexDecodeResult result;
        result.error = error;
        result.error_name = hushChatHexDecodeErrorName(error);
        return result;
    };

    if (hex.empty()) {
        if (expectedByteLength == 0) {
            HushChatHexDecodeResult result;
            result.ok = true;
            return result;
        }
        return failDecode(HushChatHexDecodeError::Empty);
    }
    if (hex.size() % 2 != 0) return failDecode(HushChatHexDecodeError::OddLength);
    if (!isHexString(hex)) return failDecode(HushChatHexDecodeError::InvalidHex);
    if (hex.size() / 2 != expectedByteLength) {
        return failDecode(HushChatHexDecodeError::UnexpectedByteLength);
    }

    HushChatHexDecodeResult result;
    result.ok = true;
    result.bytes.reserve(expectedByteLength);
    for (std::size_t index = 0; index < hex.size(); index += 2) {
        auto high = hexNibble(static_cast<unsigned char>(hex[index]));
        auto low = hexNibble(static_cast<unsigned char>(hex[index + 1]));
        if (!high || !low) return failDecode(HushChatHexDecodeError::InvalidHex);
        result.bytes.push_back(static_cast<unsigned char>((*high << 4) | *low));
    }
    return result;
}

HushChatDecryptInputPreparationResult prepareHushChatDecryptInput(
    const HushChatDecryptInputMaterial& material,
    bool featureEnabled)
{
    auto failPreparation = [&](HushChatDecryptInputError error,
                               HushChatHexDecodeError hexError = HushChatHexDecodeError::None,
                               HushChatDecryptPreflightError preflightError = HushChatDecryptPreflightError::None) {
        HushChatDecryptInputPreparationResult result;
        result.feature_enabled = featureEnabled;
        result.error = error;
        result.error_name = hushChatDecryptInputErrorName(error);
        result.hex_error = hexError;
        result.preflight_error = preflightError;
        return result;
    };

    if (!featureEnabled) return failPreparation(HushChatDecryptInputError::FeatureDisabled);

    auto storedChatKey = decodeHushChatHexBytes(material.stored_chat_key_hex,
                                                kHushChatStoredChatKeyByteLength);
    if (!storedChatKey.ok) {
        return failPreparation(HushChatDecryptInputError::InvalidStoredChatKey, storedChatKey.error);
    }

    auto preflight = validateHushChatMemoDecryptPreflight(
        HushChatDecryptPreflightInput{material.header, material.ciphertext_hex},
        true);
    if (!preflight.ok) {
        return failPreparation(HushChatDecryptInputError::DecryptPreflightFailed,
                               HushChatHexDecodeError::None,
                               preflight.error);
    }

    const std::string& peerPublicKeyHex = material.peer_public_key_hex.empty()
        ? material.header.public_key_hex
        : material.peer_public_key_hex;
    auto peerPublicKey = decodeHushChatHexBytes(peerPublicKeyHex,
                                                kHushChatPublicKeyByteLength);
    if (!peerPublicKey.ok) {
        return failPreparation(HushChatDecryptInputError::InvalidPeerPublicKey, peerPublicKey.error);
    }

    auto streamHeader = decodeHushChatHexBytes(material.header.secretstream_header_hex,
                                               kHushChatSecretstreamHeaderByteLength);
    if (!streamHeader.ok) {
        return failPreparation(HushChatDecryptInputError::InvalidStreamHeader, streamHeader.error);
    }

    auto ciphertext = decodeHushChatHexBytes(material.ciphertext_hex, preflight.ciphertext_size);
    if (!ciphertext.ok) {
        return failPreparation(HushChatDecryptInputError::InvalidCiphertext, ciphertext.error);
    }

    HushChatDecryptInputPreparationResult result;
    result.ok = true;
    result.feature_enabled = true;
    result.error = HushChatDecryptInputError::None;
    result.error_name = hushChatDecryptInputErrorName(result.error);
    result.prepared.stored_chat_key_bytes = std::move(storedChatKey.bytes);
    result.prepared.seed_bytes.reserve(kHushChatSeedByteLength);
    for (std::size_t index = 0; index < kHushChatSeedByteLength; ++index) {
        result.prepared.seed_bytes.push_back(static_cast<unsigned char>(material.stored_chat_key_hex[index]));
    }
    result.prepared.peer_public_key_bytes = std::move(peerPublicKey.bytes);
    result.prepared.stream_header_bytes = std::move(streamHeader.bytes);
    result.prepared.ciphertext_bytes = std::move(ciphertext.bytes);
    result.prepared.direction = material.direction;
    result.prepared.session_key_selection = hushChatSessionKeySelectionForDirection(material.direction);
    result.prepared.plaintext_capacity = result.prepared.ciphertext_bytes.size() - kHushChatSecretstreamABytes;
    return result;
}

HushChatDecryptFixtureReadinessResult inspectHushChatDecryptFixtureReadiness(
    const HushChatPreparedDecryptInput& prepared)
{
    HushChatDecryptFixtureReadinessResult result;
    result.stored_chat_key_size = prepared.stored_chat_key_bytes.size();
    result.seed_size = prepared.seed_bytes.size();
    result.peer_public_key_size = prepared.peer_public_key_bytes.size();
    result.stream_header_size = prepared.stream_header_bytes.size();
    result.ciphertext_size = prepared.ciphertext_bytes.size();
    result.plaintext_capacity = prepared.plaintext_capacity;
    result.session_key_selection = prepared.session_key_selection;
    result.ready = result.stored_chat_key_size == kHushChatStoredChatKeyByteLength &&
                   result.seed_size == kHushChatSeedByteLength &&
                   result.peer_public_key_size == kHushChatPublicKeyByteLength &&
                   result.stream_header_size == kHushChatSecretstreamHeaderByteLength &&
                   result.ciphertext_size > kHushChatSecretstreamABytes &&
                   result.plaintext_capacity == result.ciphertext_size - kHushChatSecretstreamABytes;
    return result;
}

HushChatCompatibilityFixtureVerificationResult verifyHushChatCompatibilityFixture(
    const HushChatCompatibilityFixture& fixture,
    bool featureEnabled)
{
    auto failFixture = [&](HushChatCompatibilityFixtureError error,
                           HushChatHexDecodeError hexError = HushChatHexDecodeError::None) {
        HushChatCompatibilityFixtureVerificationResult result;
        result.feature_enabled = featureEnabled;
        result.error = error;
        result.error_name = hushChatCompatibilityFixtureErrorName(error);
        result.hex_error = hexError;
        return result;
    };

    if (!featureEnabled) return failFixture(HushChatCompatibilityFixtureError::FeatureDisabled);
    if (fixture.fixture_id.empty()) return failFixture(HushChatCompatibilityFixtureError::MissingFixtureId);

    auto localPublicKey = decodeHushChatHexBytes(fixture.local_public_key_hex,
                                                 kHushChatPublicKeyByteLength);
    if (!localPublicKey.ok) {
        return failFixture(HushChatCompatibilityFixtureError::InvalidLocalPublicKey, localPublicKey.error);
    }

    auto peerPublicKey = decodeHushChatHexBytes(fixture.peer_public_key_hex,
                                                kHushChatPublicKeyByteLength);
    if (!peerPublicKey.ok) {
        return failFixture(HushChatCompatibilityFixtureError::InvalidPeerPublicKey, peerPublicKey.error);
    }

    auto parsedHeader = parseHushChatHeaderMemo(fixture.header_memo);
    if (!parsedHeader.ok) return failFixture(HushChatCompatibilityFixtureError::InvalidHeaderMemo);

    auto grouped = groupHushChatMemoOutputs({
        HushChatMemoOutput{0, fixture.header_memo},
        HushChatMemoOutput{1, fixture.ciphertext_memo}
    });
    if (grouped.pairs.size() != 1 || !grouped.issues.empty()) {
        return failFixture(HushChatCompatibilityFixtureError::InvalidMemoPair);
    }

    const auto& header = grouped.pairs[0].header;
    if (header.type != HushChatHeaderType::Message) {
        return failFixture(HushChatCompatibilityFixtureError::NonMemoHeader);
    }

    const std::string& expectedHeaderPublicKey = fixture.direction == HushChatDecryptDirection::Incoming
        ? fixture.peer_public_key_hex
        : fixture.local_public_key_hex;
    if (header.public_key_hex != expectedHeaderPublicKey) {
        return failFixture(HushChatCompatibilityFixtureError::HeaderPublicKeyMismatch);
    }

    auto preparation = prepareHushChatDecryptInput(HushChatDecryptInputMaterial{
        fixture.stored_chat_key_hex,
        header,
        fixture.ciphertext_memo,
        fixture.direction,
        fixture.peer_public_key_hex
    }, true);

    if (!preparation.ok) {
        HushChatCompatibilityFixtureVerificationResult result = failFixture(
            HushChatCompatibilityFixtureError::DecryptInputFailed,
            preparation.hex_error);
        result.decrypt_input_error = preparation.error;
        result.preflight_error = preparation.preflight_error;
        result.preparation = std::move(preparation);
        return result;
    }

    auto readiness = inspectHushChatDecryptFixtureReadiness(preparation.prepared);
    if (!readiness.ready) {
        HushChatCompatibilityFixtureVerificationResult result = failFixture(
            HushChatCompatibilityFixtureError::NotFixtureReady);
        result.preparation = std::move(preparation);
        result.readiness = readiness;
        return result;
    }

    auto failExpectation = [&](HushChatCompatibilityFixtureError error,
                               HushChatDecryptInputPreparationResult&& prepared,
                               const HushChatDecryptFixtureReadinessResult& fixtureReadiness) {
        HushChatCompatibilityFixtureVerificationResult result = failFixture(error);
        result.header = header;
        result.preparation = std::move(prepared);
        result.readiness = fixtureReadiness;
        result.local_public_key_size = localPublicKey.bytes.size();
        result.peer_public_key_size = peerPublicKey.bytes.size();
        return result;
    };

    if (readiness.stored_chat_key_size != fixture.expected_stored_chat_key_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedStoredChatKeyLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (readiness.seed_size != fixture.expected_seed_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedSeedLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (localPublicKey.bytes.size() != fixture.expected_local_public_key_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedLocalPublicKeyLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (readiness.peer_public_key_size != fixture.expected_peer_public_key_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedPeerPublicKeyLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (readiness.stream_header_size != fixture.expected_stream_header_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedStreamHeaderLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (readiness.ciphertext_size != fixture.expected_ciphertext_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedCiphertextLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (readiness.plaintext_capacity != fixture.expected_plaintext_size) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedPlaintextLengthMismatch,
                               std::move(preparation),
                               readiness);
    }
    if (readiness.session_key_selection != fixture.expected_session_key_selection) {
        return failExpectation(HushChatCompatibilityFixtureError::ExpectedRoleMismatch,
                               std::move(preparation),
                               readiness);
    }

    std::size_t plaintextHashSize = 0;
    if (!fixture.expected_plaintext_hash_hex.empty()) {
        if (fixture.expected_plaintext_hash_hex.size() % 2 != 0 ||
            !isHexString(fixture.expected_plaintext_hash_hex)) {
            return failExpectation(HushChatCompatibilityFixtureError::InvalidPlaintextHash,
                                   std::move(preparation),
                                   readiness);
        }
        plaintextHashSize = fixture.expected_plaintext_hash_hex.size() / 2;
    }

    HushChatCompatibilityFixtureVerificationResult result;
    result.ok = true;
    result.feature_enabled = true;
    result.error = HushChatCompatibilityFixtureError::None;
    result.error_name = hushChatCompatibilityFixtureErrorName(result.error);
    result.header = header;
    result.preparation = std::move(preparation);
    result.readiness = readiness;
    result.local_public_key_size = localPublicKey.bytes.size();
    result.peer_public_key_size = peerPublicKey.bytes.size();
    result.plaintext_hash_size = plaintextHashSize;
    return result;
}

HushChatCompatibilityFixtureFileParseResult parseHushChatCompatibilityFixtureFile(
    const std::string& jsonText,
    bool featureEnabled)
{
    auto failFile = [&](HushChatCompatibilityFixtureFileError error) {
        HushChatCompatibilityFixtureFileParseResult result;
        result.feature_enabled = featureEnabled;
        result.error = error;
        result.error_name = hushChatCompatibilityFixtureFileErrorName(error);
        return result;
    };

    if (!featureEnabled) return failFile(HushChatCompatibilityFixtureFileError::FeatureDisabled);

    nlohmann::json object;
    try {
        object = nlohmann::json::parse(jsonText);
    } catch (const nlohmann::json::parse_error&) {
        return failFile(HushChatCompatibilityFixtureFileError::InvalidJson);
    }
    if (!object.is_object()) return failFile(HushChatCompatibilityFixtureFileError::JsonNotObject);

    auto schema = readJsonString(object, "schema");
    if (!schema || *schema != kHushChatCompatibilityFixtureSchema) {
        return failFile(HushChatCompatibilityFixtureFileError::InvalidSchema);
    }

    auto kindText = readJsonString(object, "kind");
    if (!kindText) return failFile(HushChatCompatibilityFixtureFileError::MissingKind);
    auto kind = parseFixtureKind(*kindText);
    if (!kind) return failFile(HushChatCompatibilityFixtureFileError::UnknownKind);

    auto statusText = readJsonString(object, "status");
    if (!statusText) return failFile(HushChatCompatibilityFixtureFileError::MissingStatus);
    auto status = parseFixtureFileStatus(*statusText);
    if (!status) return failFile(HushChatCompatibilityFixtureFileError::UnknownStatus);

    HushChatCompatibilityFixtureFile parsed;
    parsed.schema = *schema;
    parsed.kind = *kind;
    parsed.status = *status;
    if (auto fixtureId = readJsonString(object, "id")) parsed.fixture_id = *fixtureId;

    if (parsed.status == HushChatCompatibilityFixtureFileStatus::Pending) {
        if (parsed.fixture_id.empty()) return failFile(HushChatCompatibilityFixtureFileError::MissingFixtureId);

        auto pendingReason = readJsonString(object, "pending_reason");
        if (!pendingReason || pendingReason->empty()) {
            return failFile(HushChatCompatibilityFixtureFileError::MissingPendingReason);
        }
        parsed.pending_reason = *pendingReason;

        HushChatCompatibilityFixtureFileParseResult result;
        result.ok = true;
        result.feature_enabled = true;
        result.pending = true;
        result.error = HushChatCompatibilityFixtureFileError::None;
        result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error);
        result.file = std::move(parsed);
        return result;
    }

    if (!object.contains("fixture") || !object["fixture"].is_object()) {
        return failFile(HushChatCompatibilityFixtureFileError::MissingFixtureObject);
    }

    const auto& fixtureObject = object["fixture"];
    const nlohmann::json expected = fixtureObject.contains("expected") ? fixtureObject["expected"] : nlohmann::json();
    if (!expected.is_object()) return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField);

    auto readRequiredFixtureString = [&](const nlohmann::json& source, const char* key) -> std::optional<std::string> {
        auto value = readJsonString(source, key);
        if (!value || value->empty()) return std::nullopt;
        return value;
    };

    HushChatCompatibilityFixture fixture;
    auto id = readRequiredFixtureString(fixtureObject, "id");
    auto storedChatKey = readRequiredFixtureString(fixtureObject, "stored_chat_key_hex");
    auto localPublicKey = readRequiredFixtureString(fixtureObject, "local_public_key_hex");
    auto peerPublicKey = readRequiredFixtureString(fixtureObject, "peer_public_key_hex");
    auto headerMemo = readRequiredFixtureString(fixtureObject, "header_memo");
    auto ciphertextMemo = readRequiredFixtureString(fixtureObject, "ciphertext_memo");
    auto directionText = readRequiredFixtureString(fixtureObject, "direction");
    auto roleText = readRequiredFixtureString(expected, "session_key_selection");
    auto storedKeySize = readJsonSize(expected, "stored_chat_key_bytes");
    auto seedSize = readJsonSize(expected, "seed_bytes");
    auto localKeySize = readJsonSize(expected, "local_public_key_bytes");
    auto peerKeySize = readJsonSize(expected, "peer_public_key_bytes");
    auto streamHeaderSize = readJsonSize(expected, "stream_header_bytes");
    auto ciphertextSize = readJsonSize(expected, "ciphertext_bytes");
    auto plaintextSize = readJsonSize(expected, "plaintext_bytes");
    if (!id || !storedChatKey || !localPublicKey || !peerPublicKey || !headerMemo || !ciphertextMemo ||
        !directionText || !roleText || !storedKeySize || !seedSize || !localKeySize || !peerKeySize ||
        !streamHeaderSize || !ciphertextSize || !plaintextSize) {
        return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField);
    }

    auto direction = parseFixtureDirection(*directionText);
    auto role = parseFixtureSessionKeySelection(*roleText);
    if (!direction || !role) return failFile(HushChatCompatibilityFixtureFileError::InvalidFixtureField);

    fixture.fixture_id = *id;
    fixture.stored_chat_key_hex = *storedChatKey;
    fixture.local_public_key_hex = *localPublicKey;
    fixture.peer_public_key_hex = *peerPublicKey;
    fixture.header_memo = *headerMemo;
    fixture.ciphertext_memo = *ciphertextMemo;
    fixture.direction = *direction;
    fixture.expected_session_key_selection = *role;
    fixture.expected_stored_chat_key_size = *storedKeySize;
    fixture.expected_seed_size = *seedSize;
    fixture.expected_local_public_key_size = *localKeySize;
    fixture.expected_peer_public_key_size = *peerKeySize;
    fixture.expected_stream_header_size = *streamHeaderSize;
    fixture.expected_ciphertext_size = *ciphertextSize;
    fixture.expected_plaintext_size = *plaintextSize;
    if (auto plaintextHash = readJsonString(expected, "plaintext_hash_hex")) {
        fixture.expected_plaintext_hash_hex = *plaintextHash;
    }

    parsed.fixture_id = fixture.fixture_id;
    parsed.fixture = fixture;

    auto verification = verifyHushChatCompatibilityFixture(fixture, true);
    if (parsed.kind == HushChatCompatibilityFixtureKind::ContactExclusion) {
        if (verification.error != HushChatCompatibilityFixtureError::NonMemoHeader) {
            HushChatCompatibilityFixtureFileParseResult result = failFile(
                HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded);
            result.file = std::move(parsed);
            result.verification = std::move(verification);
            return result;
        }

        HushChatCompatibilityFixtureFileParseResult result;
        result.ok = true;
        result.feature_enabled = true;
        result.excluded_from_decrypt = true;
        result.error = HushChatCompatibilityFixtureFileError::None;
        result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error);
        result.file = std::move(parsed);
        result.verification = std::move(verification);
        return result;
    }

    if (!verification.ok) {
        HushChatCompatibilityFixtureFileParseResult result = failFile(
            HushChatCompatibilityFixtureFileError::FixtureVerificationFailed);
        result.file = std::move(parsed);
        result.verification = std::move(verification);
        return result;
    }

    HushChatCompatibilityFixtureFileParseResult result;
    result.ok = true;
    result.feature_enabled = true;
    result.verified = true;
    result.error = HushChatCompatibilityFixtureFileError::None;
    result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error);
    result.file = std::move(parsed);
    result.verification = std::move(verification);
    return result;
}

HushChatCompatibilityFixtureFileParseResult loadHushChatCompatibilityFixtureFile(
    const std::string& path,
    bool featureEnabled)
{
    if (!featureEnabled) {
        HushChatCompatibilityFixtureFileParseResult result;
        result.error = HushChatCompatibilityFixtureFileError::FeatureDisabled;
        result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error);
        return result;
    }

    std::ifstream input(path);
    if (!input.good()) {
        HushChatCompatibilityFixtureFileParseResult result;
        result.feature_enabled = true;
        result.error = HushChatCompatibilityFixtureFileError::FileReadFailed;
        result.error_name = hushChatCompatibilityFixtureFileErrorName(result.error);
        return result;
    }

    std::ostringstream buffer;
    buffer << input.rdbuf();
    return parseHushChatCompatibilityFixtureFile(buffer.str(), true);
}

HushChatSeedPublicKeyProjectionResult verifyHushChatSeedPublicKeyProjection(
    const HushChatCompatibilityFixture& fixture,
    bool featureEnabled)
{
    auto failProjection = [&](HushChatSeedPublicKeyProjectionError error,
                              HushChatHexDecodeError hexError = HushChatHexDecodeError::None) {
        HushChatSeedPublicKeyProjectionResult result;
        result.feature_enabled = featureEnabled;
        result.error = error;
        result.error_name = hushChatSeedPublicKeyProjectionErrorName(error);
        result.hex_error = hexError;
        return result;
    };

    if (!featureEnabled) return failProjection(HushChatSeedPublicKeyProjectionError::FeatureDisabled);
    if (fixture.fixture_id.empty()) return failProjection(HushChatSeedPublicKeyProjectionError::MissingFixtureId);

    auto storedChatKey = decodeHushChatHexBytes(fixture.stored_chat_key_hex,
                                                kHushChatStoredChatKeyByteLength);
    if (!storedChatKey.ok) {
        return failProjection(HushChatSeedPublicKeyProjectionError::InvalidStoredChatKey, storedChatKey.error);
    }

    auto localPublicKey = decodeHushChatHexBytes(fixture.local_public_key_hex,
                                                 kHushChatPublicKeyByteLength);
    if (!localPublicKey.ok) {
        return failProjection(HushChatSeedPublicKeyProjectionError::InvalidLocalPublicKey, localPublicKey.error);
    }

    const std::size_t seedSize = std::min(fixture.stored_chat_key_hex.size(), kHushChatSeedByteLength);
    if (storedChatKey.bytes.size() != fixture.expected_stored_chat_key_size) {
        auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedStoredChatKeyLengthMismatch);
        result.stored_chat_key_size = storedChatKey.bytes.size();
        result.seed_size = seedSize;
        result.local_public_key_size = localPublicKey.bytes.size();
        return result;
    }
    if (seedSize != fixture.expected_seed_size) {
        auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedSeedLengthMismatch);
        result.stored_chat_key_size = storedChatKey.bytes.size();
        result.seed_size = seedSize;
        result.local_public_key_size = localPublicKey.bytes.size();
        return result;
    }
    if (localPublicKey.bytes.size() != fixture.expected_local_public_key_size) {
        auto result = failProjection(HushChatSeedPublicKeyProjectionError::ExpectedLocalPublicKeyLengthMismatch);
        result.stored_chat_key_size = storedChatKey.bytes.size();
        result.seed_size = seedSize;
        result.local_public_key_size = localPublicKey.bytes.size();
        return result;
    }

    if (sodium_init() < 0) {
        return failProjection(HushChatSeedPublicKeyProjectionError::SodiumInitializationFailed);
    }

    unsigned char seed[kHushChatSeedByteLength];
    for (std::size_t index = 0; index < kHushChatSeedByteLength; ++index) {
        seed[index] = static_cast<unsigned char>(fixture.stored_chat_key_hex[index]);
    }

    unsigned char projectedPublicKey[crypto_kx_PUBLICKEYBYTES];
    unsigned char projectedSecretKey[crypto_kx_SECRETKEYBYTES];
    const int projectionStatus = crypto_kx_seed_keypair(projectedPublicKey, projectedSecretKey, seed);
    sodium_memzero(projectedSecretKey, sizeof(projectedSecretKey));
    sodium_memzero(seed, sizeof(seed));
    if (projectionStatus != 0) {
        return failProjection(HushChatSeedPublicKeyProjectionError::KeypairProjectionFailed);
    }

    HushChatSeedPublicKeyProjectionResult result;
    result.feature_enabled = true;
    result.stored_chat_key_size = storedChatKey.bytes.size();
    result.seed_size = seedSize;
    result.local_public_key_size = localPublicKey.bytes.size();
    result.projected_public_key_size = sizeof(projectedPublicKey);

    if (result.projected_public_key_size != localPublicKey.bytes.size() ||
        std::memcmp(projectedPublicKey, localPublicKey.bytes.data(), localPublicKey.bytes.size()) != 0) {
        sodium_memzero(projectedPublicKey, sizeof(projectedPublicKey));
        result.error = HushChatSeedPublicKeyProjectionError::ProjectedPublicKeyMismatch;
        result.error_name = hushChatSeedPublicKeyProjectionErrorName(result.error);
        return result;
    }

    sodium_memzero(projectedPublicKey, sizeof(projectedPublicKey));
    result.ok = true;
    result.error = HushChatSeedPublicKeyProjectionError::None;
    result.error_name = hushChatSeedPublicKeyProjectionErrorName(result.error);
    return result;
}

HushChatCorruptedAuthFailureReadinessResult inspectHushChatCorruptedAuthFailureReadiness(
    const HushChatCompatibilityFixtureFileParseResult& parsed,
    const HushChatSeedPublicKeyProjectionResult& seedProjection,
    bool featureEnabled)
{
    auto failReadiness = [&](HushChatCorruptedAuthFailureReadinessError error) {
        HushChatCorruptedAuthFailureReadinessResult result;
        result.feature_enabled = featureEnabled;
        result.error = error;
        result.error_name = hushChatCorruptedAuthFailureReadinessErrorName(error);
        return result;
    };

    if (!featureEnabled) return failReadiness(HushChatCorruptedAuthFailureReadinessError::FeatureDisabled);
    if (parsed.pending) return failReadiness(HushChatCorruptedAuthFailureReadinessError::FixturePending);
    if (parsed.file.kind != HushChatCompatibilityFixtureKind::CorruptedAuthFailure) {
        return failReadiness(HushChatCorruptedAuthFailureReadinessError::WrongFixtureKind);
    }
    if (!parsed.verified || !parsed.verification.ok) {
        return failReadiness(HushChatCorruptedAuthFailureReadinessError::FixtureNotVerified);
    }
    if (!seedProjection.ok) {
        return failReadiness(HushChatCorruptedAuthFailureReadinessError::SeedProjectionNotVerified);
    }

    HushChatCorruptedAuthFailureReadinessResult result;
    result.ok = true;
    result.feature_enabled = true;
    result.structurally_ready_for_future_auth_check = true;
    result.requires_future_secretstream_auth_failure = true;
    result.decrypted = false;
    result.authenticated = false;
    result.error = HushChatCorruptedAuthFailureReadinessError::None;
    result.error_name = hushChatCorruptedAuthFailureReadinessErrorName(result.error);
    return result;
}

std::vector<HushChatCompatibilityFixtureKind> hushChatRequiredCompatibilityFixtureKinds()
{
    return {
        HushChatCompatibilityFixtureKind::IncomingMemo,
        HushChatCompatibilityFixtureKind::OutgoingMemo,
        HushChatCompatibilityFixtureKind::SeedPublicKeyProjection,
        HushChatCompatibilityFixtureKind::CorruptedAuthFailure,
        HushChatCompatibilityFixtureKind::ContactExclusion
    };
}

HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist(
    const std::vector<HushChatCompatibilityFixtureImportCandidate>& candidates,
    bool featureEnabled)
{
    HushChatCompatibilityFixtureImportChecklistResult result;
    result.feature_enabled = featureEnabled;

    auto setFirstError = [&](HushChatCompatibilityFixtureImportError error) {
        if (result.error == HushChatCompatibilityFixtureImportError::None) {
            result.error = error;
            result.error_name = hushChatCompatibilityFixtureImportErrorName(error);
        }
    };

    if (!featureEnabled) {
        setFirstError(HushChatCompatibilityFixtureImportError::FeatureDisabled);
        return result;
    }

    const auto requiredKinds = hushChatRequiredCompatibilityFixtureKinds();
    result.required_count = requiredKinds.size();
    result.items.reserve(requiredKinds.size());

    for (const auto expectedKind : requiredKinds) {
        HushChatCompatibilityFixtureImportItem item;
        item.expected_kind = expectedKind;

        const HushChatCompatibilityFixtureImportCandidate* selected = nullptr;
        std::size_t matchCount = 0;
        for (const auto& candidate : candidates) {
            if (candidate.expected_kind != expectedKind) continue;
            ++matchCount;
            if (!selected) selected = &candidate;
        }

        if (matchCount == 0) {
            item.error = HushChatCompatibilityFixtureImportError::MissingRequiredKind;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.missing_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        item.supplied = true;
        ++result.supplied_count;

        if (matchCount > 1 || !selected) {
            item.error = HushChatCompatibilityFixtureImportError::DuplicateKind;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.rejected_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        item.path = selected->path;
        item.parsed = loadHushChatCompatibilityFixtureFile(selected->path, true);
        if (!item.parsed.ok) {
            if (item.parsed.error == HushChatCompatibilityFixtureFileError::FileReadFailed) {
                item.error = HushChatCompatibilityFixtureImportError::FixtureLoadFailed;
            } else if (item.parsed.error == HushChatCompatibilityFixtureFileError::ContactFixtureNotExcluded) {
                item.error = HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded;
            } else {
                item.error = HushChatCompatibilityFixtureImportError::FixtureInvalid;
            }
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.rejected_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        item.loaded_kind = item.parsed.file.kind;
        if (item.loaded_kind != expectedKind) {
            item.error = HushChatCompatibilityFixtureImportError::FixtureKindMismatch;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.rejected_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        if (item.parsed.pending) {
            item.pending = true;
            item.error = HushChatCompatibilityFixtureImportError::FixturePending;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.pending_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        if (expectedKind == HushChatCompatibilityFixtureKind::ContactExclusion) {
            if (!item.parsed.excluded_from_decrypt) {
                item.error = HushChatCompatibilityFixtureImportError::ContactFixtureNotExcluded;
                item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
                ++result.rejected_count;
                setFirstError(item.error);
                result.items.push_back(std::move(item));
                continue;
            }

            item.replacement_eligible = true;
            item.error = HushChatCompatibilityFixtureImportError::None;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.excluded_count;
            result.items.push_back(std::move(item));
            continue;
        }

        if (!item.parsed.verified) {
            item.error = HushChatCompatibilityFixtureImportError::FixtureNotVerified;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.rejected_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        item.seed_projection = verifyHushChatSeedPublicKeyProjection(item.parsed.file.fixture, true);
        if (!item.seed_projection.ok) {
            item.error = HushChatCompatibilityFixtureImportError::SeedProjectionFailed;
            item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
            ++result.rejected_count;
            setFirstError(item.error);
            result.items.push_back(std::move(item));
            continue;
        }

        if (expectedKind == HushChatCompatibilityFixtureKind::CorruptedAuthFailure) {
            item.auth_failure_readiness = inspectHushChatCorruptedAuthFailureReadiness(
                item.parsed,
                item.seed_projection,
                true);
            if (!item.auth_failure_readiness.ok) {
                item.error = HushChatCompatibilityFixtureImportError::AuthFailureScaffoldFailed;
                item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
                ++result.rejected_count;
                setFirstError(item.error);
                result.items.push_back(std::move(item));
                continue;
            }

            item.future_auth_failure_required = item.auth_failure_readiness.requires_future_secretstream_auth_failure;
            item.structurally_ready_for_future_auth_check =
                item.auth_failure_readiness.structurally_ready_for_future_auth_check;
            if (item.future_auth_failure_required) ++result.future_auth_failure_required_count;
            if (item.structurally_ready_for_future_auth_check) ++result.auth_failure_structural_ready_count;
        }

        item.replacement_eligible = true;
        item.seed_projection_verified = true;
        item.error = HushChatCompatibilityFixtureImportError::None;
        item.error_name = hushChatCompatibilityFixtureImportErrorName(item.error);
        ++result.verified_count;
        ++result.seed_projection_verified_count;
        result.items.push_back(std::move(item));
    }

    result.replacement_ready = result.supplied_count == result.required_count &&
                               result.missing_count == 0 &&
                               result.pending_count == 0 &&
                               result.rejected_count == 0 &&
                               result.verified_count == result.required_count - 1 &&
                               result.seed_projection_verified_count == result.required_count - 1 &&
                               result.future_auth_failure_required_count == 1 &&
                               result.auth_failure_structural_ready_count == 1 &&
                               result.excluded_count == 1;
    result.ok = result.replacement_ready;
    if (result.ok) {
        result.error = HushChatCompatibilityFixtureImportError::None;
        result.error_name = hushChatCompatibilityFixtureImportErrorName(result.error);
    }
    return result;
}

HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun(
    const std::vector<HushChatCompatibilityFixtureImportCandidate>& candidates,
    bool featureEnabled)
{
    const auto checklist = inspectHushChatCompatibilityFixtureImportChecklist(candidates, featureEnabled);

    HushChatCompatibilityFixtureReplacementDryRunResult result;
    result.ok = checklist.replacement_ready;
    result.feature_enabled = checklist.feature_enabled;
    result.would_replace = checklist.replacement_ready;
    result.replacement_refused = !checklist.replacement_ready;
    result.error = checklist.error;
    result.error_name = checklist.error_name;
    result.required_count = checklist.required_count;
    result.supplied_count = checklist.supplied_count;
    result.missing_count = checklist.missing_count;
    result.pending_count = checklist.pending_count;
    result.verified_count = checklist.verified_count;
    result.seed_projection_verified_count = checklist.seed_projection_verified_count;
    result.future_auth_failure_required_count = checklist.future_auth_failure_required_count;
    result.auth_failure_structural_ready_count = checklist.auth_failure_structural_ready_count;
    result.excluded_count = checklist.excluded_count;
    result.rejected_count = checklist.rejected_count;
    result.report_items.reserve(checklist.items.size());

    for (const auto& item : checklist.items) {
        HushChatCompatibilityFixtureReplacementReportItem reportItem;
        reportItem.expected_kind = item.expected_kind;
        reportItem.loaded_kind = item.loaded_kind;
        reportItem.path = item.path;
        reportItem.supplied = item.supplied;
        reportItem.pending = item.pending;
        reportItem.replacement_eligible = item.replacement_eligible;
        reportItem.refused = !item.replacement_eligible;
        reportItem.seed_projection_verified = item.seed_projection_verified;
        reportItem.future_auth_failure_required = item.future_auth_failure_required;
        reportItem.structurally_ready_for_future_auth_check = item.structurally_ready_for_future_auth_check;
        reportItem.cont_excluded = item.parsed.excluded_from_decrypt;
        reportItem.decrypted = item.auth_failure_readiness.decrypted;
        reportItem.authenticated = item.auth_failure_readiness.authenticated;
        reportItem.error = item.error;
        reportItem.error_name = item.error_name;
        result.report_items.push_back(std::move(reportItem));
    }

    return result;
}

HushChatCaptureManifestValidationResult validateHushChatCaptureManifest(
    const std::string& jsonText,
    bool featureEnabled)
{
    HushChatCaptureManifestValidationResult result;
    result.feature_enabled = featureEnabled;
    const auto requiredKinds = hushChatRequiredCompatibilityFixtureKinds();
    result.required_count = requiredKinds.size();
    result.categories.reserve(requiredKinds.size());
    for (const auto kind : requiredKinds) {
        HushChatCaptureManifestCategoryReport report;
        report.kind = kind;
        result.categories.push_back(std::move(report));
    }

    auto failManifest = [&](HushChatCaptureManifestError error) {
        result.error = error;
        result.error_name = hushChatCaptureManifestErrorName(error);
        return result;
    };

    if (!featureEnabled) return failManifest(HushChatCaptureManifestError::FeatureDisabled);

    nlohmann::json object;
    try {
        object = nlohmann::json::parse(jsonText);
    } catch (const nlohmann::json::parse_error&) {
        return failManifest(HushChatCaptureManifestError::InvalidJson);
    }
    if (!object.is_object()) return failManifest(HushChatCaptureManifestError::JsonNotObject);

    result.prohibited_field_count = countProhibitedCaptureManifestFields(object);
    if (result.prohibited_field_count > 0) {
        return failManifest(HushChatCaptureManifestError::ProhibitedFieldPresent);
    }

    auto schema = readJsonString(object, "schema");
    if (!schema || *schema != kHushChatCaptureManifestSchema) {
        return failManifest(HushChatCaptureManifestError::InvalidSchema);
    }

    auto id = readJsonString(object, "id");
    if (!id || id->empty()) return failManifest(HushChatCaptureManifestError::MissingManifestId);

    auto status = readJsonString(object, "status");
    if (!status) return failManifest(HushChatCaptureManifestError::MissingStatus);
    if (*status != "staged") return failManifest(HushChatCaptureManifestError::UnknownStatus);

    auto fixtureDirectory = readJsonString(object, "fixture_directory");
    if (!fixtureDirectory || fixtureDirectory->empty()) {
        return failManifest(HushChatCaptureManifestError::MissingFixtureDirectory);
    }
    result.fixture_directory = *fixtureDirectory;

    auto dryRunCommand = readJsonString(object, "dry_run_command");
    if (!dryRunCommand || dryRunCommand->empty()) {
        return failManifest(HushChatCaptureManifestError::MissingDryRunCommand);
    }
    result.has_dry_run_command = true;
    if (dryRunCommand->find("HushChatFixtureCheck") == std::string::npos ||
        dryRunCommand->find("--replacement-dry-run") == std::string::npos) {
        return failManifest(HushChatCaptureManifestError::InvalidDryRunCommand);
    }

    auto provenance = object.find("provenance");
    if (provenance == object.end() || !provenance->is_object()) {
        return failManifest(HushChatCaptureManifestError::MissingProvenance);
    }
    auto sourceClient = readJsonString(*provenance, "source_client");
    if (!sourceClient || sourceClient->empty()) {
        return failManifest(HushChatCaptureManifestError::MissingSourceClient);
    }
    if (*sourceClient != "SilentDragonXLite") {
        return failManifest(HushChatCaptureManifestError::InvalidSourceClient);
    }
    auto sourceClientVersion = readJsonString(*provenance, "source_client_version");
    if (!sourceClientVersion || sourceClientVersion->empty()) {
        return failManifest(HushChatCaptureManifestError::MissingSourceClientVersion);
    }
    auto captureDate = readJsonString(*provenance, "capture_date");
    if (!captureDate || captureDate->empty()) return failManifest(HushChatCaptureManifestError::MissingCaptureDate);
    auto network = readJsonString(*provenance, "network");
    if (!network || network->empty()) return failManifest(HushChatCaptureManifestError::MissingNetwork);
    auto captureMethod = readJsonString(*provenance, "capture_method");
    if (!captureMethod || captureMethod->empty()) {
        return failManifest(HushChatCaptureManifestError::MissingCaptureMethod);
    }

    auto handling = object.find("handling");
    if (handling == object.end() || !handling->is_object()) {
        return failManifest(HushChatCaptureManifestError::MissingHandling);
    }
    for (const char* flag : requiredCaptureHandlingFlags()) {
        auto flagValue = handling->find(flag);
        if (flagValue == handling->end()) return failManifest(HushChatCaptureManifestError::MissingHandlingFlag);
        if (!flagValue->is_boolean() || !flagValue->get<bool>()) {
            return failManifest(HushChatCaptureManifestError::HandlingFlagNotTrue);
        }
        ++result.handling_flag_count;
    }
    result.no_sensitive_material_declared = true;

    auto categories = object.find("categories");
    if (categories == object.end() || !categories->is_array()) {
        return failManifest(HushChatCaptureManifestError::MissingCategories);
    }

    auto findCategoryReport = [&](HushChatCompatibilityFixtureKind kind) -> HushChatCaptureManifestCategoryReport* {
        for (auto& report : result.categories) {
            if (report.kind == kind) return &report;
        }
        return nullptr;
    };

    for (const auto& category : *categories) {
        if (!category.is_object()) return failManifest(HushChatCaptureManifestError::InvalidCategoryEntry);

        auto kindText = readJsonString(category, "kind");
        auto stagedFilename = readJsonString(category, "staged_filename");
        auto categoryStatus = readJsonString(category, "status");
        if (!kindText || kindText->empty() || !stagedFilename || stagedFilename->empty() ||
            !categoryStatus || *categoryStatus != "ready") {
            return failManifest(HushChatCaptureManifestError::InvalidCategoryEntry);
        }

        auto kind = parseFixtureKind(*kindText);
        if (!kind) return failManifest(HushChatCaptureManifestError::UnknownCategory);

        auto* report = findCategoryReport(*kind);
        if (!report) return failManifest(HushChatCaptureManifestError::UnknownCategory);
        if (report->declared) {
            ++result.duplicate_count;
            return failManifest(HushChatCaptureManifestError::DuplicateCategory);
        }

        report->declared = true;
        report->staged_filename = *stagedFilename;
        ++result.declared_count;
    }

    for (const auto& report : result.categories) {
        if (!report.declared) ++result.missing_count;
    }
    if (result.missing_count > 0) return failManifest(HushChatCaptureManifestError::MissingRequiredCategory);

    result.ok = true;
    result.error = HushChatCaptureManifestError::None;
    result.error_name = hushChatCaptureManifestErrorName(result.error);
    return result;
}

HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile(
    const std::string& path,
    bool featureEnabled)
{
    if (!featureEnabled) {
        HushChatCaptureManifestValidationResult result;
        result.error = HushChatCaptureManifestError::FeatureDisabled;
        result.error_name = hushChatCaptureManifestErrorName(result.error);
        return result;
    }

    std::ifstream input(path);
    if (!input.good()) {
        HushChatCaptureManifestValidationResult result;
        result.feature_enabled = true;
        result.manifest_path = path;
        result.error = HushChatCaptureManifestError::FileReadFailed;
        result.error_name = hushChatCaptureManifestErrorName(result.error);
        return result;
    }

    std::ostringstream buffer;
    buffer << input.rdbuf();
    auto result = validateHushChatCaptureManifest(buffer.str(), true);
    result.manifest_path = path;
    return result;
}

} // namespace dragonx::chat
