#include "wallet/lite_sync_service.h"

#include <algorithm>
#include <limits>
#include <utility>

namespace dragonx::wallet {
namespace {

std::uint64_t effectiveStagnantThreshold(std::uint64_t threshold)
{
    return threshold == 0 ? 1 : threshold;
}

WalletBackendStatus makeSyncScaffoldStatus(WalletBackendState state,
                                           std::string message,
                                           const LiteSyncStatusResponse* syncStatus = nullptr)
{
    WalletBackendStatus status;
    status.state = state;
    status.message = std::move(message);
    if (syncStatus) {
        status.walletHeight = static_cast<int>(std::min<std::uint64_t>(syncStatus->syncedBlocks, static_cast<std::uint64_t>(std::numeric_limits<int>::max())));
        status.chainHeight = static_cast<int>(std::min<std::uint64_t>(syncStatus->totalBlocks, static_cast<std::uint64_t>(std::numeric_limits<int>::max())));
        status.syncProgress = syncStatus->progress;
    }
    return status;
}

} // namespace

const char* liteSyncOperationName(LiteSyncOperation operation)
{
    switch (operation) {
        case LiteSyncOperation::StartSync: return "StartSync";
        case LiteSyncOperation::PollSyncStatus: return "PollSyncStatus";
    }
    return "Unknown";
}

const char* liteSyncStartModeName(LiteSyncStartMode mode)
{
    switch (mode) {
        case LiteSyncStartMode::Startup: return "Startup";
        case LiteSyncStartMode::Restore: return "Restore";
        case LiteSyncStartMode::Rescan: return "Rescan";
        case LiteSyncStartMode::Recovery: return "Recovery";
    }
    return "Unknown";
}

const char* liteSyncAvailabilityName(LiteSyncAvailability availability)
{
    switch (availability) {
        case LiteSyncAvailability::Ready: return "Ready";
        case LiteSyncAvailability::UnsupportedBuild: return "UnsupportedBuild";
        case LiteSyncAvailability::BackendUnavailable: return "BackendUnavailable";
        case LiteSyncAvailability::BridgeUnavailable: return "BridgeUnavailable";
        case LiteSyncAvailability::BridgeCallsDisabled: return "BridgeCallsDisabled";
        case LiteSyncAvailability::NoUsableServer: return "NoUsableServer";
    }
    return "Unknown";
}

const char* liteSyncRecoveryDecisionKindName(LiteSyncRecoveryDecisionKind kind)
{
    switch (kind) {
        case LiteSyncRecoveryDecisionKind::KeepPolling: return "KeepPolling";
        case LiteSyncRecoveryDecisionKind::SyncComplete: return "SyncComplete";
        case LiteSyncRecoveryDecisionKind::Stuck: return "Stuck";
        case LiteSyncRecoveryDecisionKind::ReorgDetected: return "ReorgDetected";
        case LiteSyncRecoveryDecisionKind::InvalidStatus: return "InvalidStatus";
    }
    return "Unknown";
}

WalletBackendStatus walletStatusFromLiteSyncStatus(const LiteSyncStatusResponse& syncStatus)
{
    if (syncStatus.complete) {
        return makeSyncScaffoldStatus(
            WalletBackendState::Ready,
            "lite syncstatus reports sync complete",
            &syncStatus);
    }
    return makeSyncScaffoldStatus(
        WalletBackendState::Syncing,
        "lite syncstatus reports sync in progress",
        &syncStatus);
}

LiteSyncRecoveryDecision evaluateLiteSyncRecovery(const LiteSyncRecoveryInput& input)
{
    if (input.totalBlocks > 0 && input.syncedBlocks > input.totalBlocks) {
        return LiteSyncRecoveryDecision{
            LiteSyncRecoveryDecisionKind::InvalidStatus,
            false,
            false,
            false,
            false,
            true,
            "synced block count is greater than total block count"
        };
    }

    if (input.havePreviousSyncedBlocks && input.syncedBlocks < input.previousSyncedBlocks) {
        return LiteSyncRecoveryDecision{
            LiteSyncRecoveryDecisionKind::ReorgDetected,
            false,
            true,
            true,
            true,
            false,
            "synced block count moved backwards; model clear/rescan recovery"
        };
    }

    if (input.totalBlocks > 0 && input.syncedBlocks >= input.totalBlocks) {
        return LiteSyncRecoveryDecision{
            LiteSyncRecoveryDecisionKind::SyncComplete,
            false,
            false,
            false,
            false,
            false,
            "syncstatus reports all blocks synced"
        };
    }

    const auto threshold = effectiveStagnantThreshold(input.stagnantPollThreshold);
    if (input.havePreviousSyncedBlocks &&
        input.syncedBlocks == input.previousSyncedBlocks &&
        input.stagnantPollCount >= threshold) {
        return LiteSyncRecoveryDecision{
            LiteSyncRecoveryDecisionKind::Stuck,
            false,
            false,
            false,
            true,
            false,
            "synced block count has not advanced past the configured threshold"
        };
    }

    return LiteSyncRecoveryDecision{
        LiteSyncRecoveryDecisionKind::KeepPolling,
        true,
        false,
        false,
        false,
        false,
        "syncstatus is usable; keep polling"
    };
}

LiteSyncService::LiteSyncService(WalletCapabilities capabilities,
                                 LiteConnectionSettings connectionSettings,
                                 LiteClientBridge* bridge,
                                 LiteSyncServiceOptions options)
    : capabilities_(capabilities),
      connectionSettings_(std::move(connectionSettings)),
      bridge_(bridge),
      options_(options)
{
}

LiteSyncAvailability LiteSyncService::availability() const
{
    if (!isLiteBuild(capabilities_)) return LiteSyncAvailability::UnsupportedBuild;
    if (!supportsLiteBackend(capabilities_)) return LiteSyncAvailability::BackendUnavailable;
    if (!bridge_ || !bridge_->available()) return LiteSyncAvailability::BridgeUnavailable;
    if (!selectLiteServer(connectionSettings_).ok) return LiteSyncAvailability::NoUsableServer;
    if (!options_.allowSyncStatusBridgeCalls) return LiteSyncAvailability::BridgeCallsDisabled;
    return LiteSyncAvailability::Ready;
}

WalletBackendStatus LiteSyncService::status() const
{
    const auto currentAvailability = availability();
    if (currentAvailability == LiteSyncAvailability::NoUsableServer) {
        return statusFor(currentAvailability, selectLiteServer(connectionSettings_).error);
    }
    return statusFor(currentAvailability);
}

LiteSyncPlan LiteSyncService::planStartSync(const LiteSyncStartRequest& request) const
{
    return makePlan(
        LiteSyncOperation::StartSync,
        request.serverUrl,
        options_.allowSyncStatusBridgeCalls,
        request.mode,
        request.forceRescan,
        request.afterRestore);
}

LiteSyncPlan LiteSyncService::planSyncStatus(const LiteSyncStatusRequest& request) const
{
    return makePlan(
        LiteSyncOperation::PollSyncStatus,
        request.serverUrl,
        options_.allowSyncStatusBridgeCalls,
        LiteSyncStartMode::Startup,
        false,
        false);
}

LiteSyncStartResult LiteSyncService::startSync(const LiteSyncStartRequest& request)
{
    const auto plan = planStartSync(request);
    if (!plan.ok) return blockedStartResult(plan, makeSyncScaffoldStatus(WalletBackendState::Error, plan.error));

    const auto currentAvailability = availability();
    if (currentAvailability == LiteSyncAvailability::UnsupportedBuild ||
        currentAvailability == LiteSyncAvailability::BackendUnavailable ||
        currentAvailability == LiteSyncAvailability::BridgeUnavailable ||
        currentAvailability == LiteSyncAvailability::NoUsableServer) {
        return blockedStartResult(plan, status());
    }

    LiteSyncStartResult result;
    result.plan = plan;
    result.attempted = true;

    const auto bridgeCall = bridge_->execute(plan.command, plan.args);
    if (!bridgeCall.ok) {
        result.status = makeSyncScaffoldStatus(WalletBackendState::Error, "lite sync start bridge call failed");
        result.error = result.status.message;
        return result;
    }

    result.ok = true;
    result.syncStarted = true;
    result.status = makeSyncScaffoldStatus(WalletBackendState::Syncing, "lite sync started");
    return result;
}

LiteSyncStatusResult LiteSyncService::pollSyncStatus(const LiteSyncStatusRequest& request)
{
    const auto plan = planSyncStatus(request);
    if (!plan.ok) return blockedStatusResult(plan, makeSyncScaffoldStatus(WalletBackendState::Error, plan.error));

    if (availability() != LiteSyncAvailability::Ready) return blockedStatusResult(plan, status());

    LiteSyncStatusResult result;
    result.plan = plan;
    result.attempted = true;

    const auto bridgeCall = bridge_->execute(plan.command, plan.args);
    result.bridgeResponseRedacted = bridgeCall.ok || !bridgeCall.error.empty() ? "<redacted>" : "<empty>";
    if (!bridgeCall.ok) {
        result.status = makeSyncScaffoldStatus(WalletBackendState::Error, "lite syncstatus bridge call failed");
        result.error = result.status.message;
        return result;
    }

    result.bridgeAccepted = true;
    const auto parsed = parseLiteSyncStatusResponse(bridgeCall.value);
    if (!parsed.ok) {
        result.parserError = parsed.error;
        result.status = makeSyncScaffoldStatus(WalletBackendState::Error, "lite syncstatus response could not be parsed");
        result.error = result.status.message;
        return result;
    }

    result.ok = true;
    result.parserError = LiteResultParserError::None;
    result.syncStatus = parsed.syncStatus;
    result.status = walletStatusFromLiteSyncStatus(result.syncStatus);
    return result;
}

LiteServerSelectionResult LiteSyncService::selectServerForRequest(const std::string& serverUrl) const
{
    const auto overrideUrl = liteTrimCopy(serverUrl);
    if (!overrideUrl.empty()) {
        if (!isLiteServerUrlUsable(overrideUrl)) {
            return LiteServerSelectionResult{false, {}, 0, false, "lite sync server URL is not usable"};
        }
        return LiteServerSelectionResult{
            true,
            LiteServerEndpoint{overrideUrl, "Request", true},
            0,
            true,
            {}
        };
    }
    return selectLiteServer(connectionSettings_);
}

LiteSyncPlan LiteSyncService::makePlan(LiteSyncOperation operation,
                                       const std::string& serverUrl,
                                       bool bridgeExecutionAllowed,
                                       LiteSyncStartMode startMode,
                                       bool forceRescan,
                                       bool afterRestore) const
{
    LiteSyncPlan plan;
    plan.operation = operation;
    plan.startMode = startMode;
    plan.bridgeExecutionAllowed = bridgeExecutionAllowed;
    plan.forceRescan = forceRescan;
    plan.afterRestore = afterRestore;
    plan.command = operation == LiteSyncOperation::StartSync ? "sync" : "syncstatus";

    auto selection = selectServerForRequest(serverUrl);
    if (!selection.ok) {
        plan.error = selection.error;
        return plan;
    }

    plan.ok = true;
    plan.server = selection.server;
    plan.serverIndex = selection.serverIndex;
    plan.customServer = selection.customServer;
    return plan;
}

WalletBackendStatus LiteSyncService::statusFor(LiteSyncAvailability availability,
                                               const std::string& detail) const
{
    switch (availability) {
        case LiteSyncAvailability::Ready:
            return makeSyncScaffoldStatus(
                WalletBackendState::Disconnected,
                detail.empty() ? "lite sync scaffold ready; sync is not started" : detail);
        case LiteSyncAvailability::UnsupportedBuild:
            return makeSyncScaffoldStatus(
                WalletBackendState::Unavailable,
                "lite sync service is unsupported in full-node builds");
        case LiteSyncAvailability::BackendUnavailable:
            return makeSyncScaffoldStatus(
                WalletBackendState::Unavailable,
                "lite backend is not linked");
        case LiteSyncAvailability::BridgeUnavailable:
            return makeSyncScaffoldStatus(
                WalletBackendState::Unavailable,
                detail.empty() ? (bridge_ ? bridge_->unavailableReason() : "lite bridge is unavailable") : detail);
        case LiteSyncAvailability::BridgeCallsDisabled:
            return makeSyncScaffoldStatus(
                WalletBackendState::Unavailable,
                "lite syncstatus bridge calls are disabled");
        case LiteSyncAvailability::NoUsableServer:
            return makeSyncScaffoldStatus(
                WalletBackendState::Error,
                detail.empty() ? "no usable lite servers are configured" : detail);
    }

    return makeSyncScaffoldStatus(WalletBackendState::Unavailable, "unknown lite sync state");
}

LiteSyncStartResult LiteSyncService::blockedStartResult(const LiteSyncPlan& plan,
                                                        const WalletBackendStatus& blockedStatus) const
{
    LiteSyncStartResult result;
    result.plan = plan;
    result.status = blockedStatus;
    result.error = blockedStatus.message;
    return result;
}

LiteSyncStatusResult LiteSyncService::blockedStatusResult(const LiteSyncPlan& plan,
                                                          const WalletBackendStatus& blockedStatus) const
{
    LiteSyncStatusResult result;
    result.plan = plan;
    result.status = blockedStatus;
    result.error = blockedStatus.message;
    return result;
}

} // namespace dragonx::wallet