#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "Core/Models.h"
#include "Core/RuntimeSession.h"
#include "Memory/VmmAccessor.h"
#include "Readers/BulletTableReader.h"
#include "Readers/ClientScoreboardReader.h"
#include "Readers/EntityCategoryProjector.h"
#include "Readers/FarEntityListReader.h"
#include "Readers/ItemFilterCatalog.h"
#include "Readers/ItemListReader.h"
#include "Readers/NearEntityListReader.h"
#include "Readers/SlowEntityListReader.h"
#include "Resolvers/BaseObjectResolver.h"
#include "Resolvers/LocalPlayerResolver.h"
#include "Resolvers/NetworkMetadataReader.h"
#include "SigScanner/SigScanner.h"

// -------------------------------------------------------------------------
// RuntimeUpdate
// Snapshot of all reader state delivered to the callback on each cycle.
// -------------------------------------------------------------------------

struct RuntimeUpdate {
    bool        areBaseObjectsReady    = false;
    std::string status;

    std::optional<std::string> serverName;
    std::optional<std::string> gameVersion;
    std::optional<std::string> serverMapName;

    std::optional<uint64_t> localPlayerEntityAddress;
    std::optional<Vector3>  localPlayerPosition;
    std::optional<float>    localPlayerLookDirection;

    std::vector<DayZScoreboardPlayer>  scoreboardPlayers;
    std::vector<DayZPlayerEntry>       players;
    std::vector<DayZAnimalEntry>       animals;
    std::vector<DayZZombieEntry>       zombies;
    std::vector<DayZCarAndBoatEntry>   carsAndBoats;
    std::vector<DayZOtherEntityEntry>  otherEntities;
    std::vector<DayZBulletEntry>       bullets;
    std::vector<DayZItemListEntry>     items;
    std::vector<DayZNearEntityEntry>   nearEntities;
    std::vector<DayZFarEntityEntry>    farEntities;
    std::vector<DayZSlowEntityEntry>   slowEntities;

    std::optional<CameraData>          camera;
};

// -------------------------------------------------------------------------
// RuntimeConfig
// Tunable parameters for the service.
// -------------------------------------------------------------------------

struct RuntimeConfig {
    std::string processName  = "DayZ_x64.exe";
    bool        useMemoryMap = true;   // passed to VolkDMA; auto-generates memory_map.txt

    int networkMetadataRefreshMs = 30000;
    int scoreboardRefreshMs      = 30000;
    int nearEntityRefreshMs      = 20;   // was 50 — 50 Hz keeps boxes smooth in combat
    int farEntityRefreshMs       = 50;   // was 100
    int slowEntityRefreshMs      = 100;
    int bulletRefreshMs          = 50;
    int itemRefreshMs            = 200;
    int playersRefreshMs         = 16;   // was 50 — matches bone-scatter cadence (60 Hz rebuild)
    int liveLoopIdleMs           = 1;
    int reconnectThreshold       = 5;
    int reconnectGracePeriodMs   = 5000;
    int farEntityBatchSize       = 64;
    int slowEntityBatchSize      = 64;
    int itemBatchSize            = 64;
    int   boneRefreshMs            = 4;    // bone-scatter cadence — 4 ms = 250 Hz
    float bonePlayerMaxDist        = 500.0f; // players beyond this skip bone reads
    float boneZombieMaxDist        = 250.0f; // zombies beyond this skip bone reads
    int   vmmRefreshMs             = 30000; // VMMDLL_OPT_REFRESH_ALL cadence — must be called
                                            // periodically during the live loop or the page cache
                                            // goes stale and reads silently return garbage (~1 h)
    float itemSpatialFilterRadius  = 300.0f; // items beyond this distance from the local player are
                                             // excluded from the published snapshot entirely

    /// Load from an INI-style config.cfg (key = value, # comments).
    /// Missing keys keep their default values.
    static RuntimeConfig Load(const std::string& path);
};

// -------------------------------------------------------------------------
// DayZRuntimeService
// Background service that attaches to DayZ, reads all entity/bullet/item
// data in a throttled loop, and delivers snapshots via a callback.
// -------------------------------------------------------------------------

class DayZRuntimeService {
public:
    using UpdateCallback = std::function<void(const RuntimeUpdate&)>;

    explicit DayZRuntimeService(RuntimeConfig config = {});
    ~DayZRuntimeService();

    // Non-copyable, non-movable (owns threads + mutexes)
    DayZRuntimeService(const DayZRuntimeService&)            = delete;
    DayZRuntimeService& operator=(const DayZRuntimeService&) = delete;
    DayZRuntimeService(DayZRuntimeService&&)                 = delete;
    DayZRuntimeService& operator=(DayZRuntimeService&&)      = delete;

    /// Start the background thread.  Safe to call only once.
    void Start();

    /// Signal the background thread to stop and join it.
    void Stop();

    /// Thread-safe snapshot of the most recent entity update (50 ms cadence).
    [[nodiscard]] std::shared_ptr<const RuntimeUpdate> GetLatestUpdate() const;

    /// Lightweight camera read — fills out with the latest camera data.
    /// Updated every background loop tick (~1 ms), completely separate from the
    /// entity snapshot so the overlay always gets the freshest camera.
    /// Returns false if no valid camera is available yet.
    bool GetLatestCamera(CameraData& out) const;

    /// Register a callback invoked (from the background thread) on each update.
    void SetUpdateCallback(UpdateCallback cb);

private:
    // ------------------------------------------------------------------
    // Configuration
    // ------------------------------------------------------------------
    RuntimeConfig   m_config;
    ItemFilterCatalog m_filterCatalog;

    // ------------------------------------------------------------------
    // Memory + resolvers
    // ------------------------------------------------------------------
    VmmAccessor             m_memory;
    BaseObjectResolver      m_baseResolver;
    LocalPlayerResolver     m_localPlayerResolver;
    NetworkMetadataReader   m_networkMetadataReader;
    NearEntityListReader    m_nearReader;
    FarEntityListReader     m_farReader;
    SlowEntityListReader    m_slowReader;
    BulletTableReader       m_bulletReader;
    ItemListReader          m_itemReader;
    ClientScoreboardReader  m_scoreboardReader;

    // Last module base address passed to the sig scanner.
    uint64_t                m_lastScannedBase = 0;

    // Cached camera base pointer (resolved once per session; 0 = not yet known).
    // Caching this avoids re-reading World+0x1B8 on every tick.
    uint64_t                m_cachedCamBase   = 0;

    // Resolved skeleton pointer-chain offsets (probed once, cached per session).
    // Skel offsets are per entity-type (player vs zombie differ); anim/matrix are shared.
    int64_t                 m_bonePlayerSkelOffset = -1;
    int64_t                 m_boneZombieSkelOffset = -1;
    int64_t                 m_boneAnimOffset       = -1;
    int64_t                 m_boneMatrixOffset     = -1;

    // Per-entity bone pointer cache: entity address → (vsAddr, animClass, matBase).
    // animClass is stable for the entity lifetime; matBase is dynamic (can be
    // reallocated by the engine's animation system).  Caching animClass separately
    // lets re-resolve after a stale matBase skip the full 4-read pointer walk.
    struct BonePointerCache {
        uint64_t vsAddr    = 0;
        uint64_t animClass = 0;
        uint64_t matBase   = 0;
        bool     valid     = false;
        uint32_t syncAge   = 0;  // bone-tick counter; matBase re-read when this reaches kMatBaseResyncTicks
    };
    std::unordered_map<uint64_t, BonePointerCache> m_bonePointerCache;

    // ---- Diagnostic logging bookkeeping ----
    // Admins already announced to the log (so each is reported once, not every
    // cycle).  Cleared when the player leaves the snapshot so a re-join re-logs.
    std::unordered_set<uint64_t>              m_adminAnnounced;
    // Entities whose skeleton currently resolves to valid bones.  Used to detect
    // the valid→invalid edge so we can log when a skeleton drops out.
    std::unordered_set<uint64_t>              m_bonesTracked;
    // addr → steady-clock ms of the last "skeleton lost" log, for debouncing
    // flapping skeletons to at most one message every few seconds.
    std::unordered_map<uint64_t, int64_t>     m_boneLostLogAt;

    // Separate lightweight camera state for the overlay.
    // Updated by the dedicated camera thread (RunCameraLoop) at high frequency,
    // decoupled from the heavy entity/bone reads so the overlay always projects
    // through the freshest possible camera — this is what keeps boxes/skeletons
    // glued to fast viewangle flicks instead of lagging a few ms behind.
    mutable std::mutex      m_camMutex;
    CameraData              m_latestCamForOverlay;

    // Session handoff for the camera thread.  Set when a live session starts,
    // cleared (0) on reconnect so the camera thread idles while the Process
    // handle is being re-created.  Atomics: written by the runtime thread,
    // read by the camera thread.
    std::atomic<uint32_t>   m_camPid{0};
    std::atomic<uint64_t>   m_camWorldAddr{0};

    // ------------------------------------------------------------------
    // Threading
    // ------------------------------------------------------------------
    std::thread             m_runtimeThread;
    std::thread             m_cameraThread;
    std::atomic<bool>       m_stopRequested{false};
    mutable std::mutex                          m_updateMutex;
    std::shared_ptr<const RuntimeUpdate>        m_latestUpdate;
    UpdateCallback                              m_callback;

    // ------------------------------------------------------------------
    // Live update baseline — entity lists refreshed every playersRefreshMs,
    // camera/localPlayer overwritten every loop tick.
    RuntimeUpdate           m_liveUpdate;

    // ------------------------------------------------------------------
    // Per-iteration reader state / throttle timestamps
    // ------------------------------------------------------------------
    struct ReaderState {
        std::vector<DayZScoreboardPlayer> scoreboard;
        std::vector<DayZNearEntityEntry>  nearEntities;
        std::vector<DayZFarEntityEntry>   farEntities;
        std::vector<DayZSlowEntityEntry>  slowEntities;
        std::vector<DayZBulletEntry>      bullets;
        std::vector<DayZItemListEntry>    items;

        std::chrono::steady_clock::time_point nextScoreboardRefresh;
        std::chrono::steady_clock::time_point nextNearRefresh;
        std::chrono::steady_clock::time_point nextFarRefresh;
        std::chrono::steady_clock::time_point nextSlowRefresh;
        std::chrono::steady_clock::time_point nextBulletRefresh;
        std::chrono::steady_clock::time_point nextItemRefresh;
        std::chrono::steady_clock::time_point nextPlayersRefresh;
        std::chrono::steady_clock::time_point nextNetworkMetadataRefresh;
        std::chrono::steady_clock::time_point nextBoneRefresh;
        std::chrono::steady_clock::time_point nextVmmRefresh;
    } m_state;

    // Cached metadata, player data, and camera populated during session
    NetworkMetadata m_metadata;
    LocalPlayerData m_localPlayer;
    CameraData      m_camera;

    // ------------------------------------------------------------------
    // Exception used to trigger a reconnect from RunLiveLoop
    // ------------------------------------------------------------------
    struct ReconnectException {};

    // ------------------------------------------------------------------
    // Private methods
    // ------------------------------------------------------------------
    void Run();

    /// Block until a valid session is established. Returns the session.
    RuntimeSession WaitForSession();

    /// Main live-data loop.  Throws ReconnectException when the session
    /// is no longer valid or the consecutive-failure threshold is reached.
    void RunLiveLoop(const RuntimeSession& session);

    /// Dedicated high-frequency camera reader.  Runs for the whole program
    /// lifetime; idles until m_camPid / m_camWorldAddr are set by a live session.
    void RunCameraLoop();

    /// Read the camera transform block into `out`.  Resolves camBaseCache from
    /// worldAddr when it is 0; resets it to 0 on a failed read to force a
    /// re-resolve next call.  Returns true on success.
    bool ReadCameraInto(uint32_t pid, uint64_t worldAddr,
                        uint64_t& camBaseCache, CameraData& out);

    /// Publish an update snapshot under the mutex and invoke the callback.
    void PublishUpdate(const RuntimeUpdate& update);

    /// Reset all readers and clear m_state vectors.
    void ResetAllReaders();

    /// Build a scoreboard lookup map (networkId -> playerName).
    static std::unordered_map<uint32_t, std::string>
    BuildScoreboardNames(const std::vector<DayZScoreboardPlayer>& scoreboard);

    /// Scatter-read bones for all players/zombies in range using the pointer cache.
    /// Cache misses resolve the pointer chain sequentially (one-time per entity).
    /// All subsequent reads for cached entities are batched into one DMA call.
    void RefreshBonesScatter(uint32_t pid,
                             std::vector<DayZPlayerEntry>& players,
                             std::vector<DayZZombieEntry>& zombies);
};
