#include "adapters/eft/items/interop/EftItemGateway.h"

#include <algorithm>
#include <string>
#include <utility>

#include "adapters/eft/items/actions/ItemActions.h"
#include "adapters/hooks/items/ItemHookState.h"
#include "application/lifecycle/RaidContext.h"
#include "foundation/diagnostics/ExecutionPath.h"

namespace ta::adapters::eft::items
{
namespace
{
    ta::features::items::contracts::ItemCatalogSnapshot build_catalog_snapshot(
        const ta::adapters::eft::items::ItemTemplateCatalogService& catalog,
        const ta::features::items::contracts::WishlistSnapshot& wishlist)
    {
        ta::features::items::contracts::ItemCatalogSnapshot snapshot{};
        snapshot.items = catalog.entries();
        snapshot.static_catalog_size = snapshot.items.size();
        snapshot.loading = catalog.loading();
        snapshot.ready = catalog.ready();
        snapshot.index_by_id.reserve(snapshot.items.size());
        for (std::size_t index = 0; index < snapshot.items.size(); ++index)
        {
            auto& item = snapshot.items[index];
            item.wishlisted = wishlist.user_items.contains(item.id);
            snapshot.index_by_id.emplace(item.id, index);
        }
        return snapshot;
    }
}

EftItemGateway::EftItemGateway(ta::adapters::eft::runtime::EftGameContext& context,
                               ta::adapters::eft::items::WishlistService& wishlist_service,
                               ta::adapters::eft::items::ItemTemplateCatalogService& runtime_catalog)
    : context_(context), runtime_catalog_(runtime_catalog), wishlist_service_(wishlist_service)
{
    catalog_snapshot_.publish(build_catalog_snapshot(runtime_catalog_, wishlist_service_.current()));
}


void EftItemGateway::application_tick(const ta::features::items::contracts::ItemSettings& settings)
{
    ta::adapters::hooks::items::set_no_slot_limits(settings.no_slot_limits);
    ta::adapters::hooks::items::set_increased_transfer_size(settings.increased_transfer_size);
}

void EftItemGateway::game_tick(const ta::features::items::contracts::ItemSettings& settings,
                               std::vector<ta::features::items::contracts::ItemSelectorCommand> selector_commands,
                               std::optional<ta::features::items::contracts::ItemActionCommand> action)
{
    process_selector_commands(std::move(selector_commands));
    refresh_selector_catalog();
    if (action)
        queue_action(settings, std::move(*action));
    const auto context = context_.snapshot();
    dispatch_item_create_request(context.game_world_instance, context.main_player);
}

void EftItemGateway::process_selector_commands(std::vector<ta::features::items::contracts::ItemSelectorCommand> commands)
{
    for (const ta::features::items::contracts::ItemSelectorCommand& command : commands)
    {
        if (command.type == ta::features::items::contracts::ItemSelectorCommandType::ReloadCatalog)
        {
            reload_requested_ = true;
            continue;
        }

        const bool activated = command.active && !selector_active_;
        selector_active_ = command.active;
        if (activated)
        {
            refresh_counter_ = 0;
            wishlist_service_.request_refresh();
            catalog_revision_ = 0;
        }
    }
}

void EftItemGateway::refresh_selector_catalog()
{
    if (reload_requested_)
    {
        reload_requested_ = false;
        runtime_catalog_.request_reload();
        catalog_revision_ = 0;
    }

    if (selector_active_ && ++refresh_counter_ >= 300)
    {
        refresh_counter_ = 0;
        wishlist_service_.request_refresh();
    }

    const std::uint64_t current_catalog_revision = runtime_catalog_.revision();
    const std::uint64_t current_wishlist_revision = wishlist_service_.revision();
    if (current_catalog_revision != catalog_revision_ || current_wishlist_revision != wishlist_revision_)
    {
        catalog_revision_ = current_catalog_revision;
        wishlist_revision_ = current_wishlist_revision;
        catalog_snapshot_.publish(build_catalog_snapshot(runtime_catalog_, wishlist_service_.current()));
    }
}

void EftItemGateway::queue_action(const ta::features::items::contracts::ItemSettings& settings,
                                  ta::features::items::contracts::ItemActionCommand command)
{
    ta::features::items::contracts::ItemCreateRequest request = build_create_request(settings, command.action, command.trace_id);
    if (request.template_id.empty())
    {
        ta::foundation::execution_path::fail(command.trace_id, "EftItemGateway", "item template id is empty");
        return;
    }

    ta::foundation::execution_path::step(command.trace_id, "EftItemGateway", "queue create request", request.template_id);
    if (std::optional<ta::features::items::contracts::ItemCreateRequest> replaced = commands_.submit(std::move(request)))
        ta::foundation::execution_path::fail(replaced->trace_id, "EftItemGateway", "superseded by a newer item request");
}

ta::features::items::contracts::ItemCreateRequest EftItemGateway::build_create_request(
    const ta::features::items::contracts::ItemSettings& settings,
    const ta::features::items::contracts::PendingAction action,
    const std::uint64_t trace_id) const
{
    return {
        action,
        settings.template_id,
        settings.amount,
        runtime_catalog_.max_stack_for(settings.template_id),
        settings.override_max_amount,
        settings.found_in_raid,
        settings.overwrite_slot,
        settings.slot_index,
        trace_id
    };
}

void EftItemGateway::dispatch_item_create_request(ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                                                  ta::adapters::il2cpp::Il2CppObjectInstance main_player)
{
    if (!active_create_batch_)
    {
        std::optional<ta::features::items::contracts::ItemCreateRequest> request = commands_.consume();
        if (!request)
            return;

        ta::foundation::execution_path::step(request->trace_id, "EftItemGateway", "consume create request", request->template_id);
        active_create_batch_ = ActiveCreateBatch{*request, (std::max)(1, request->amount), 0};
    }

    ActiveCreateBatch& batch = *active_create_batch_;
    const int stack_limit = batch.request.override_max_amount
        ? batch.remaining
        : (std::max)(1, batch.request.max_stack_size);
    const int stack_amount = (std::min)(batch.remaining, stack_limit);

    ta::features::items::contracts::ItemCreateRequest stack_request = batch.request;
    stack_request.amount = stack_amount;
    if (!ta::adapters::eft::items::ItemActions::execute_stack(stack_request, game_world_instance, main_player))
    {
        active_create_batch_.reset();
        return;
    }

    batch.remaining -= stack_amount;
    ++batch.stacks_created;
    if (batch.remaining > 0)
    {
        ta::foundation::execution_path::step(batch.request.trace_id, "EftItemGateway", "create next stack",
                                      "remaining=" + std::to_string(batch.remaining));
        return;
    }

    ta::foundation::execution_path::complete(
        batch.request.trace_id,
        "ItemPlacementService",
        std::string(ta::features::items::contracts::to_string(batch.request.action)) +
            " | stacks=" + std::to_string(batch.stacks_created));
    active_create_batch_.reset();
}

std::shared_ptr<const ta::features::items::contracts::ItemCatalogSnapshot> EftItemGateway::catalog() const noexcept
{
    return catalog_snapshot_.latest();
}

bool EftItemGateway::in_raid() const noexcept
{
    return ta::application::raid_context::stable_in_raid();
}

void EftItemGateway::reset_session() noexcept
{
    if (auto request = commands_.consume())
        ta::foundation::execution_path::fail(request->trace_id, "EftItemGateway", "session reset before execution");
    active_create_batch_.reset();
    selector_active_ = false;
    reload_requested_ = false;
    refresh_counter_ = 0;
}
}
