#include "bootstrap/RuntimeStartup.h"

#include <algorithm>
#include <chrono>
#include <exception>
#include <filesystem>
#include <string>
#include <vector>

#include "adapters/hooks/HookDiagnostics.h"
#include "adapters/hooks/HookLifecycle.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/il2cpp/Il2CppExports.h"
#include "adapters/unity/bindings/UnityApi.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "adapters/unity/chams/resources/ChamsBundleRuntime.h"
#include "adapters/windows/ModulePathCache.h"
#include "adapters/windows/console/WindowsConsoleSession.h"
#include "adapters/windows/display/WindowsDisplayMetrics.h"
#include "adapters/windows/input/WindowsKeyInput.h"
#include "adapters/windows/localization/WindowsLocaleProvider.h"
#include "adapters/windows/rendering/Dx11Backend.h"
#include "adapters/windows/rendering/RenderDiagnostics.h"
#include "adapters/windows/storage/RuntimeStorageLayout.h"
#include "application/lifecycle/CallbackBarrier.h"
#include "application/lifecycle/RuntimeState.h"
#include "application/runtime/ApplicationLoopDiagnostics.h"
#include "bootstrap/ApplicationRuntime.h"
#include "foundation/diagnostics/BuildOptions.h"
#include "foundation/diagnostics/CrashDiagnostics.h"
#include "foundation/diagnostics/Logger.h"
#include "foundation/product/ProductInfo.h"
#include "foundation/input/KeyNames.h"
#include "foundation/time/TimeFormat.h"
#include "presentation/action_feedback/ActionFeedbackPresenter.h"
#include "presentation/MenuController.h"
#include "presentation/localization/UiLocalizationService.h"

namespace
{
    constexpr std::size_t max_retained_log_files = 20;
    // Reserve room for this session's execution, crash diagnostics,
    // optional breadcrumbs, and one execution-log rotation file.
    constexpr std::size_t current_session_log_reserve = 4;
    constexpr std::size_t retained_log_files_before_startup =
        max_retained_log_files - current_session_log_reserve;

    std::wstring make_log_session_timestamp()
    {
        return ta::foundation::time::format_local_timestamp(
            std::chrono::system_clock::now(), L"%Y%m%d_%H%M%S", L'_');
    }

    std::filesystem::path make_session_log_path(
        const std::filesystem::path& directory,
        const wchar_t* prefix,
        const std::wstring& timestamp)
    {
        return directory / (std::wstring(prefix) + timestamp + L".log");
    }

    void prune_old_logs(const std::filesystem::path& directory) noexcept
    {
        struct LogFile final
        {
            std::filesystem::path path;
            std::filesystem::file_time_type modified{};
        };

        std::error_code ec;
        std::vector<LogFile> files;
        for (std::filesystem::directory_iterator iterator(directory, ec), end;
             !ec && iterator != end;
             iterator.increment(ec))
        {
            const std::filesystem::directory_entry& entry = *iterator;
            if (!entry.is_regular_file(ec) || ec)
            {
                ec.clear();
                continue;
            }
            if (entry.path().extension() != L".log")
                continue;

            const auto modified = entry.last_write_time(ec);
            if (ec)
            {
                ec.clear();
                continue;
            }
            files.push_back(LogFile{entry.path(), modified});
        }

        std::sort(
            files.begin(),
            files.end(),
            [](const LogFile& left, const LogFile& right)
            {
                if (left.modified != right.modified)
                    return left.modified > right.modified;
                return left.path.filename().native() > right.path.filename().native();
            });

        for (std::size_t index = retained_log_files_before_startup;
             index < files.size();
             ++index)
        {
            ec.clear();
            std::filesystem::remove(files[index].path, ec);
        }
    }
}

namespace ta::bootstrap
{
    using ta::application::runtime::callback_barrier;
    using ta::application::runtime::state;

    const char* to_string(const StartupStage stage) noexcept
    {
        switch (stage)
        {
        case StartupStage::Storage: return "storage";
        case StartupStage::Localization: return "localization";
        case StartupStage::Resources: return "resources";
        case StartupStage::UnityWindow: return "unity_window";
        case StartupStage::Il2Cpp: return "il2cpp";
        case StartupStage::UnityApi: return "unity_api";
        case StartupStage::ApplicationGraph: return "application_graph";
        case StartupStage::DisplayMetrics: return "display_metrics";
        case StartupStage::DxLibraries: return "dx_libraries";
        case StartupStage::PresentHook: return "present_hook";
        case StartupStage::FeatureHooks: return "feature_hooks";
        default: return "unknown";
        }
    }

    RuntimeStartup::RuntimeStartup(
        const HMODULE dll_handle,
        std::atomic_bool& shutdown_requested,
        ta::adapters::windows::console::WindowsConsoleSession& console,
        ta::adapters::windows::input::WindowsKeyInput& key_input,
        std::unique_ptr<ApplicationRuntime>& application,
        std::unique_ptr<ta::presentation::MenuController>& menu,
        std::optional<PresentHookHandle>& present_hook,
        std::optional<FeatureHooksHandle>& feature_hooks) noexcept
        : dll_handle_(dll_handle),
          shutdown_requested_(shutdown_requested),
          console_(console),
          key_input_(key_input),
          application_(application),
          menu_(menu),
          present_hook_(present_hook),
          feature_hooks_(feature_hooks)
    {
    }

    std::optional<StartupError> RuntimeStartup::run() noexcept
    {
        ta::application::diagnostics::reset_application_loop();
        ta::adapters::hooks::diagnostics::reset();
        ta::adapters::windows::rendering::diagnostics::reset();
        ta::presentation::ActionFeedbackPresenter::reset();

        StartupStage startup_stage = StartupStage::Storage;
        try
        {
            show_console_if_enabled();

            startup_stage = StartupStage::Storage;
            if (!ta::adapters::windows::storage::initialize())
                return StartupError{startup_stage, "layout_unavailable", "runtime storage layout unavailable"};
            const auto storage = ta::adapters::windows::storage::snapshot();
            if (!storage)
                return StartupError{startup_stage, "snapshot_unavailable", "runtime storage snapshot unavailable"};

            prune_old_logs(storage->logs);
            const std::wstring log_timestamp = make_log_session_timestamp();
            const std::filesystem::path execution_log_path = make_session_log_path(
                storage->logs,
                L"execution_",
                log_timestamp);
            if (!ta::foundation::logger::set_file_output(execution_log_path))
                ta::foundation::logger::warn("STORAGE", "execution log unavailable | path=" + execution_log_path.string());
            else
                ta::foundation::logger::info("STORAGE", "execution log ready | path=" + execution_log_path.string());

            const std::filesystem::path crash_log_path = make_session_log_path(
                storage->logs,
                L"crash_diagnostics_",
                log_timestamp);
            if (ta::foundation::diagnostics::crash::initialize(crash_log_path, dll_handle_))
                ta::foundation::logger::info("STORAGE", "crash diagnostics ready | path=" + crash_log_path.string());
            else
                ta::foundation::logger::warn("STORAGE", "crash diagnostics unavailable | path=" + crash_log_path.string());

            if (const auto module_path = ta::adapters::windows::module_path_cache::file_path())
            {
                ta::foundation::logger::info(
                    "RUNTIME",
                    std::string("module loaded | path=") + module_path->string() +
                    " | flow=questbook-list-field,il2cpp-runtime-method-abi,item-template-direct-fields,native-thread-id-v6,crash-stage-diagnostics-v7,raid-teardown-latch-v8" +
                    " | built=" __DATE__ " " __TIME__);
            }
            else
            {
                ta::foundation::logger::warn(
                    "RUNTIME",
                    "module path unavailable | flow=questbook-list-field,il2cpp-runtime-method-abi,item-template-direct-fields,native-thread-id-v6,crash-stage-diagnostics-v7,raid-teardown-latch-v8");
            }

            startup_stage = StartupStage::Localization;
            if (!ta::presentation::localization::initialize(
                    storage->locales,
                    ta::adapters::windows::localization::system_locale_tag()))
            {
                return StartupError{startup_stage, "locale_unavailable", "UI localization unavailable"};
            }

            startup_stage = StartupStage::Resources;
            if (!ta::adapters::unity::chams::ChamsBundleRuntime::instance().initialize(
                    storage->root / L"resources" / L"chams.bundle"))
            {
                return StartupError{startup_stage, "chams_bundle_unavailable", "chams bundle unavailable"};
            }

            startup_stage = StartupStage::UnityWindow;
            if (!wait_for_unity_window())
                return StartupError{startup_stage, "cancelled", "startup cancelled while waiting for Unity window"};

            startup_stage = StartupStage::Il2Cpp;
            if (!ta::adapters::il2cpp::exports::init())
                return StartupError{startup_stage, "api_initialization_failed", "Il2CPP API initialization failed"};

            startup_stage = StartupStage::UnityApi;
            if (!ta::adapters::unity::init())
                return StartupError{startup_stage, "api_initialization_failed", "Unity API initialization failed"};
            ta::foundation::logger::info("RUNTIME", "unity initialized");

            startup_stage = StartupStage::ApplicationGraph;
            application_ = std::make_unique<ApplicationRuntime>();
            application_->initialize();

            startup_stage = StartupStage::DisplayMetrics;
            const auto display_metrics = ta::adapters::windows::display::query_primary_display_metrics();
            if (!display_metrics)
                return StartupError{startup_stage, "query_failed", "primary display metrics unavailable"};

            menu_ = std::make_unique<ta::presentation::MenuController>(
                application_->menu_ports(key_input_),
                *display_metrics);
            ta::foundation::logger::info("RUNTIME", "application graph initialized");

            startup_stage = StartupStage::DxLibraries;
            if (!wait_for_dx_libraries())
                return StartupError{startup_stage, "cancelled", "startup cancelled while waiting for DX libraries"};

            callback_barrier().open();

            startup_stage = StartupStage::PresentHook;
            if (!menu_ || !ta::adapters::windows::rendering::dx11_backend::install(dll_handle_, *menu_))
                return StartupError{startup_stage, "install_failed", "present hook installation failed"};
            present_hook_.emplace(PresentHookHandle::adopt());
            ta::foundation::logger::info("RUNTIME", "present hook installed");

            const ta::adapters::hooks::RuntimeCallbacks runtime_callbacks{
                application_.get(),
                [](void* context) noexcept
                {
                    static_cast<ApplicationRuntime*>(context)->application_update();
                },
                [](void* context,
                   const Il2CppClass* game_world_class,
                   ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                   ta::adapters::il2cpp::Il2CppObjectInstance main_player) noexcept
                {
                    static_cast<ApplicationRuntime*>(context)->gameworld_update(
                        game_world_class,
                        game_world_instance,
                        main_player);
                }
            };

            startup_stage = StartupStage::FeatureHooks;
            const ta::adapters::hooks::lifecycle::InstallResult hook_result =
                ta::adapters::hooks::lifecycle::install_all(runtime_callbacks);
            if (hook_result.hook_state_active)
                feature_hooks_.emplace(FeatureHooksHandle::adopt());
            if (!hook_result.success)
            {
                return StartupError{
                    startup_stage,
                    ta::adapters::hooks::lifecycle::to_string(hook_result.stage),
                    hook_result.detail
                };
            }
            ta::foundation::logger::info("RUNTIME", "game hooks installed");

            log_startup_banner();
            return std::nullopt;
        }
        catch (const std::exception& exception)
        {
            return StartupError{startup_stage, "exception", exception.what()};
        }
        catch (...)
        {
            return StartupError{startup_stage, "unknown_exception", "unknown startup exception"};
        }
    }

    void RuntimeStartup::show_console_if_enabled() noexcept
    {
        if (!ta::foundation::diagnostics::build_options::console || console_.is_open())
            return;
        static_cast<void>(console_.open(
            std::string(ta::foundation::product_info::display_title() + " | " + ta::foundation::product_info::credits)));
    }

    void RuntimeStartup::log_startup_banner() const
    {
        int menu_key = 0x2D;
        if (application_)
            menu_key = application_->capture_ui_shell(false).menu_key;

        const std::string message =
            ta::foundation::product_info::name + " for " + ta::foundation::product_info::version + "\n" +
            ta::foundation::product_info::credits + "\n" +
            "This is FREE software! If you paid for this or had to complete some kind of task to obtain it, you were scammed!\n"
            "Menu Key: " +
            std::string(ta::foundation::input::display_name(menu_key));
    
        ta::foundation::logger::info("RUNTIME", message);
    }

    bool RuntimeStartup::wait_for_unity_window() const noexcept
    {
        ta::foundation::logger::info("RUNTIME", "waiting for Unity window");
        while (!FindWindowW(L"UnityWndClass", nullptr))
        {
            if (shutdown_requested_.load(std::memory_order_acquire) || state().is_terminal())
                return false;
            Sleep(10);
        }
        return true;
    }

    bool RuntimeStartup::wait_for_dx_libraries() const noexcept
    {
        while (!GetModuleHandle(L"d3d11.dll") || !GetModuleHandle(L"dxgi.dll"))
        {
            if (shutdown_requested_.load(std::memory_order_acquire) || state().is_terminal())
                return false;
            Sleep(50);
        }
        return true;
    }
}
