#include "bootstrap/ApplicationRuntime.h"

#include <chrono>
#include <utility>

#include "adapters/hooks/HookDiagnostics.h"
#include "adapters/hooks/items/ItemHookDiagnostics.h"
#include "adapters/windows/rendering/RenderDiagnostics.h"
#include "application/lifecycle/RaidContext.h"
#include "application/runtime/ApplicationLoopDiagnostics.h"
#include "foundation/diagnostics/Logger.h"
#include "foundation/diagnostics/CrashDiagnostics.h"

namespace ta::bootstrap
{
    ApplicationRuntime::ApplicationRuntime()
#ifdef INCLUDE_AIMBOT
        : aimbot_module_(game_context_),
          esp_module_(game_context_, wishlist_service_),
#else
        : esp_module_(game_context_, wishlist_service_),
#endif
          chams_module_(game_context_),
          player_module_(game_context_, action_dispatcher_),
          world_module_(game_context_, wishlist_service_, item_catalog_service_, action_dispatcher_),
          item_module_(game_context_, wishlist_service_, item_catalog_service_, action_dispatcher_),
          quest_module_(game_context_),
          watermark_module_()
    {
#ifdef INCLUDE_AIMBOT
        register_module(aimbot_module_.ports());
#endif
        register_module(esp_module_.ports());
        register_module(chams_module_.ports());
        register_module(player_module_.ports());
        register_module(world_module_.ports());
        register_module(item_module_.ports());
        register_module(quest_module_.ports());
        register_module(watermark_module_.ports());
        create_config_service();
        register_diagnostics();
    }

    ApplicationRuntime::~ApplicationRuntime() noexcept
    {
        reset_session();
    }

    void ApplicationRuntime::register_module(const ta::bootstrap::modules::ModulePorts& ports)
    {
        module_ports_.push_back(&ports);
        if (ports.application_tick)
            application_ticks_.add(ports.application_tick_stage, *ports.application_tick);
        if (ports.game_tick)
            game_ticks_.add(ports.game_tick_stage, *ports.game_tick);
        if (ports.config_owner)
            config_owners_.push_back(ports.config_owner);
    }

    void ApplicationRuntime::create_config_service()
    {
        config_ = std::make_unique<ta::application::config::ConfigService>(
            config_owners_, config_persistence_, locale_controller_, action_dispatcher_);
    }

    void ApplicationRuntime::register_diagnostics()
    {
        diagnostics_.add("runtime.core", [](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            snapshot.hooks = ta::adapters::hooks::diagnostics::snapshot();
            snapshot.application_loop = ta::application::diagnostics::application_loop_snapshot();
            snapshot.render = ta::adapters::windows::rendering::diagnostics::snapshot();
        });
        diagnostics_.add("config.commands", [this](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            const auto metrics = config_->diagnostics_snapshot();
            snapshot.config_commands = {metrics.commands.requested, metrics.commands.consumed};
        });
        diagnostics_.add("items.actions", [this](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            const auto metrics = item_module_.diagnostics_snapshot();
            snapshot.item_actions = {metrics.actions.requested, metrics.actions.consumed};
        });
        diagnostics_.add("quests.commands", [this](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            const auto metrics = quest_module_.diagnostics_snapshot();
            snapshot.quest_commands = {metrics.commands.requested, metrics.commands.consumed};
        });
        diagnostics_.add("player.actions", [this](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            const auto metrics = player_module_.diagnostics_snapshot();
            snapshot.player_actions = {metrics.actions.requested, metrics.actions.consumed};
        });
        diagnostics_.add("world.actions", [this](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            const auto metrics = world_module_.diagnostics_snapshot();
            snapshot.world_unlock_doors = {metrics.unlock_doors.requested, metrics.unlock_doors.consumed};
            snapshot.world_item_teleport = {metrics.item_teleport.requested, metrics.item_teleport.consumed};
        });
        diagnostics_.add("items.hooks", [](ta::ui::DiagnosticsSnapshot& snapshot)
        {
            const auto metrics = ta::adapters::hooks::items::diagnostics::snapshot();
            snapshot.item_factory_hits = metrics.factory_hits;
            snapshot.item_factory_captured = metrics.factory_captured;
        });
    }

    void ApplicationRuntime::initialize()
    {
        for (const ta::bootstrap::modules::ModulePorts* ports : module_ports_)
        {
            if (ports && ports->has_initializer())
                ports->initialize();
        }
        config_->initialize();
    }

    void ApplicationRuntime::reset_modules(const ta::bootstrap::modules::ModuleResetScope scope) noexcept
    {
        for (const ta::bootstrap::modules::ModulePorts* ports : module_ports_)
        {
            if (!ports || ports->reset_scope != scope || !ports->has_reset())
                continue;
            if (ports->reset_stage)
                ta::foundation::diagnostics::crash::set_stage(ports->reset_stage);
            ports->reset_session();
        }
    }

    void ApplicationRuntime::reset_raid_features() noexcept
    {
        using ta::foundation::diagnostics::crash::set_stage;
        ta::foundation::diagnostics::crash::write_event("raid-reset-begin");
        reset_modules(ta::bootstrap::modules::ModuleResetScope::Raid);
        game_context_.clear();
        set_stage("runtime.raid.reset.complete");
        ta::foundation::diagnostics::crash::write_event("raid-reset-complete");
    }

    void ApplicationRuntime::reset_session() noexcept
    {
        reset_raid_features();
        reset_modules(ta::bootstrap::modules::ModuleResetScope::Session);
        was_in_raid_ = false;
    }

    void ApplicationRuntime::application_update()
    {
        using ta::foundation::diagnostics::crash::set_stage;
        set_stage("runtime.application.enter");
        const auto started = std::chrono::steady_clock::now();
        const bool in_raid = ta::application::raid_context::stable_in_raid();
        ta::foundation::diagnostics::crash::set_in_raid(in_raid);
        if (was_in_raid_ != in_raid)
        {
            ta::foundation::diagnostics::crash::write_event(
                in_raid ? "application-raid-enter" : "application-raid-exit",
                was_in_raid_ ? 1U : 0U,
                in_raid ? 1U : 0U);
        }
        if (was_in_raid_ && !in_raid)
        {
            set_stage("runtime.application.raid-exit-reset");
            ta::foundation::logger::info("RUNTIME", "raid ended; resetting feature sessions");
            reset_raid_features();
        }
        was_in_raid_ = in_raid;

        set_stage("runtime.application.config.apply-pending");
        config_->apply_pending_commands();
        set_stage("runtime.application.ticks");
        application_ticks_.tick();
        set_stage("runtime.application.config.update");
        config_->update();

        set_stage("runtime.application.metrics");
        const auto elapsed = std::chrono::steady_clock::now() - started;
        ta::application::diagnostics::record_application_update_duration(
            static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count()));
        set_stage("runtime.application.complete");
    }

    void ApplicationRuntime::gameworld_update(const Il2CppClass* game_world_class,
                                               ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                                               ta::adapters::il2cpp::Il2CppObjectInstance main_player)
    {
        using ta::foundation::diagnostics::crash::set_stage;
        set_stage(
            "runtime.gameworld.enter",
            reinterpret_cast<std::uintptr_t>(game_world_class),
            reinterpret_cast<std::uintptr_t>(game_world_instance.get_instance()),
            reinterpret_cast<std::uintptr_t>(main_player.get_instance()));

        const auto started = std::chrono::steady_clock::now();
        set_stage("runtime.gameworld.catalog.update");
        item_catalog_service_.update();
        set_stage("runtime.gameworld.wishlist.update");
        wishlist_service_.update(main_player);

        set_stage("runtime.gameworld.context.publish");
        game_context_.publish(game_world_class, game_world_instance, main_player);
        set_stage("runtime.gameworld.ticks");
        game_ticks_.tick();

        set_stage("runtime.gameworld.metrics");
        const auto elapsed = std::chrono::steady_clock::now() - started;
        ta::application::diagnostics::record_gameworld_update_duration(
            static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count()));
        set_stage("runtime.gameworld.complete");
    }

    ta::ui::UiShellSnapshot ApplicationRuntime::capture_ui_shell(const bool include_diagnostics) const
    {
        ta::ui::UiShellSnapshot snapshot{};
        snapshot.valid = true;
        snapshot.in_raid = ta::application::raid_context::stable_in_raid();
        snapshot.config = config_->latest_ui_snapshot();
        if (const auto settings = config_->latest_settings())
            snapshot.menu_key = settings->menu_key;

        if (include_diagnostics)
            snapshot.diagnostics = diagnostics_.capture();
        return snapshot;
    }

    void ApplicationRuntime::submit_config_command(ta::ui::ConfigCommand command)
    {
        config_->submit(std::move(command));
    }

    ta::presentation::MenuPorts ApplicationRuntime::menu_ports(ta::application::IKeyInput& key_input) noexcept
    {
        return {
            *this,
            *this,
            key_input,
            item_module_.queries(),
            item_module_.commands(),
            esp_module_.queries(),
            quest_module_.queries(),
            quest_module_.commands(),
            watermark_module_.queries()
        };
    }
}
