#include "wallet/lite_wallet_app_refresh_coordinator.h"

#include <utility>

namespace dragonx::wallet {
namespace {

void addIssue(LiteWalletAppRefreshCoordinationResult& result,
              LiteWalletAppRefreshCoordinationIssue issue,
              std::string message)
{
    result.issues.push_back(LiteWalletAppRefreshCoordinationIssueInfo{issue, std::move(message)});
}

std::string refreshFailureMessage(const LiteWalletRefreshServiceResult& refreshResult)
{
    if (!refreshResult.error.empty()) return refreshResult.error;
    if (!refreshResult.status.message.empty()) return refreshResult.status.message;
    return "lite wallet refresh service result is not successful";
}

void copyExecutionSummary(LiteWalletAppRefreshCoordinationResult& result)
{
    result.stateMutationRequested = result.executionResult.stateMutationRequested;
    result.stateMutationAllowed = result.applyPlan.stateMutationAllowed ||
                                  result.executionResult.stateMutationAllowed;
    result.stateMutated = result.executionResult.stateMutated;
    result.walletStateWritten = result.stateMutated;
    result.dryRunOnly = result.applyPlan.dryRunOnly && result.executionResult.dryRunOnly;
    result.noNetwork = result.executionResult.noNetwork;
    result.fieldPlanCount = result.executionResult.fieldPlanCount;
    result.collectionPlanCount = result.executionResult.collectionPlanCount;
    result.plannedChangeCount = result.executionResult.plannedChangeCount;
    result.planIssueCount = result.executionResult.planIssueCount;
    result.executionIssueCount = result.executionResult.issues.size();
}

} // namespace

const char* liteWalletAppRefreshCoordinationStatusName(
    LiteWalletAppRefreshCoordinationStatus status)
{
    switch (status) {
        case LiteWalletAppRefreshCoordinationStatus::DryRunReported: return "DryRunReported";
        case LiteWalletAppRefreshCoordinationStatus::Rejected: return "Rejected";
        case LiteWalletAppRefreshCoordinationStatus::ApplyUnavailable: return "ApplyUnavailable";
    }
    return "Unknown";
}

const char* liteWalletAppRefreshCoordinationIssueName(
    LiteWalletAppRefreshCoordinationIssue issue)
{
    switch (issue) {
        case LiteWalletAppRefreshCoordinationIssue::RefreshResultFailed: return "RefreshResultFailed";
        case LiteWalletAppRefreshCoordinationIssue::FullNodeRefreshDelegated: return "FullNodeRefreshDelegated";
        case LiteWalletAppRefreshCoordinationIssue::MappingFailed: return "MappingFailed";
        case LiteWalletAppRefreshCoordinationIssue::ApplyPlanFailed: return "ApplyPlanFailed";
        case LiteWalletAppRefreshCoordinationIssue::ApplyExecutionRejected: return "ApplyExecutionRejected";
        case LiteWalletAppRefreshCoordinationIssue::StateMutationDisabled: return "StateMutationDisabled";
        case LiteWalletAppRefreshCoordinationIssue::StateMutationImplementationMissing: return "StateMutationImplementationMissing";
    }
    return "Unknown";
}

LiteWalletAppRefreshCoordinationResult coordinateLiteWalletAppRefresh(
    const LiteWalletRefreshServiceResult& refreshResult,
    const dragonx::WalletState& walletState,
    LiteWalletAppRefreshCoordinatorOptions options)
{
    LiteWalletAppRefreshCoordinationResult result;
    result.route = refreshResult.plan.route;
    result.refreshStatus = refreshResult.status;
    result.refreshAttempted = refreshResult.attempted;
    result.fullNodeRefreshDelegated = refreshResult.fullNodeRefreshDelegated;
    result.liteGatewayCalled = refreshResult.liteGatewayCalled;
    result.stateMutationRequested = options.executorOptions.requestStateMutation;

    if (refreshResult.fullNodeRefreshDelegated) {
        addIssue(result,
                 LiteWalletAppRefreshCoordinationIssue::FullNodeRefreshDelegated,
                 "full-node refresh remains delegated; lite app refresh coordinator did not run");
        result.error = result.issues.back().message;
        return result;
    }

    if (!refreshResult.ok) {
        addIssue(result,
                 LiteWalletAppRefreshCoordinationIssue::RefreshResultFailed,
                 refreshFailureMessage(refreshResult));
        result.error = result.issues.back().message;
        return result;
    }

    result.refreshAccepted = true;
    result.mapResult = mapLiteWalletRefreshServiceResult(refreshResult);
    result.mapIssueCount = result.mapResult.issues.size();
    if (!result.mapResult.ok) {
        addIssue(result,
                 LiteWalletAppRefreshCoordinationIssue::MappingFailed,
                 result.mapResult.error.empty()
                     ? "lite wallet refresh service result could not be mapped"
                     : result.mapResult.error);
        result.error = result.issues.back().message;
        return result;
    }

    result.mapped = true;
    result.successfulCommandCount = result.mapResult.model.successfulCommandCount;
    result.applyPlan = planLiteWalletStateApply(result.mapResult.model, walletState);
    if (!result.applyPlan.ok) {
        result.fieldPlanCount = result.applyPlan.fieldPlans.size();
        result.collectionPlanCount = result.applyPlan.collectionPlans.size();
        result.planIssueCount = result.applyPlan.issues.size();
        addIssue(result,
                 LiteWalletAppRefreshCoordinationIssue::ApplyPlanFailed,
                 result.applyPlan.error.empty()
                     ? "lite WalletState apply plan could not be built"
                     : result.applyPlan.error);
        result.error = result.issues.back().message;
        return result;
    }

    result.planned = true;
    LiteWalletStateApplyExecutor executor(options.executorOptions);
    result.executionResult = executor.execute(result.applyPlan);
    result.executionReported = true;
    copyExecutionSummary(result);

    if (!result.executionResult.ok) {
        result.error = result.executionResult.error;
        if (result.executionResult.status == LiteWalletStateApplyExecutionStatus::ImplementationMissing) {
            result.status = LiteWalletAppRefreshCoordinationStatus::ApplyUnavailable;
            addIssue(result,
                     LiteWalletAppRefreshCoordinationIssue::StateMutationImplementationMissing,
                     result.executionResult.error.empty()
                         ? "real lite WalletState application requires a future explicit implementation"
                         : result.executionResult.error);
        } else {
            result.status = LiteWalletAppRefreshCoordinationStatus::Rejected;
            addIssue(result,
                     LiteWalletAppRefreshCoordinationIssue::ApplyExecutionRejected,
                     result.executionResult.error.empty()
                         ? "lite WalletState apply execution report was rejected"
                         : result.executionResult.error);
        }
        if (result.error.empty()) result.error = result.issues.back().message;
        return result;
    }

    result.ok = true;
    result.status = LiteWalletAppRefreshCoordinationStatus::DryRunReported;
    addIssue(result,
             LiteWalletAppRefreshCoordinationIssue::StateMutationDisabled,
             "lite app refresh coordinator produced a dry-run report only; WalletState was not written");
    return result;
}

LiteWalletAppRefreshCoordinator::LiteWalletAppRefreshCoordinator(
    LiteWalletAppRefreshCoordinatorOptions options)
    : options_(options)
{
}

LiteWalletAppRefreshCoordinationResult LiteWalletAppRefreshCoordinator::coordinate(
    const LiteWalletRefreshServiceResult& refreshResult,
    const dragonx::WalletState& walletState) const
{
    return coordinateLiteWalletAppRefresh(refreshResult, walletState, options_);
}

} // namespace dragonx::wallet