#include "sensor/log_sensor.hpp"
#include "memory/imemory_reader.hpp"

#include <chrono>
#include <iostream>
#include <regex>

#include <algorithm>
#include <cctype>

namespace {

uint64_t NowMs() {
    return static_cast<uint64_t>(
        std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count());
}

std::string ToLower(const std::string& s) {
    std::string res = s;
    std::transform(res.begin(), res.end(), res.begin(), [](unsigned char c) {
        return static_cast<char>(std::tolower(c));
    });
    return res;
}

// Các regex nhận dạng sự kiện - khớp docs/research/03_realtime_log_telemetry.md
const std::regex& ReAreaGenerated() {
    static const std::regex r(R"rgx(Generating level (\d+) area "([^"]+)" with seed (\d+))rgx");
    return r;
}
const std::regex& ReSceneChanged() {
    static const std::regex r(R"rgx(\[SCENE\] Set Source \[([^\]]+)\])rgx");
    return r;
}
const std::regex& ReLoadingFinished() {
    static const std::regex r(R"rgx(\[LOADING SCREEN\] \(([^)]+)\) Duration = ([\d\.]+) seconds)rgx");
    return r;
}
const std::regex& ReWhisper() {
    static const std::regex r(R"rgx(@From ([^:]+): (.*))rgx");
    return r;
}
const std::regex& ReTradeMessage() {
    static const std::regex r(R"rgx(\$([^:]+): (.*))rgx");
    return r;
}
const std::regex& ReAbnormalDisconnect() {
    static const std::regex r(R"rgx((?:Abnormal disconnect:\s*(.+)|(Disconnected from server.*|The operation timed out.*|Failed to connect to instance.*)))rgx");
    return r;
}
const std::regex& ReAccountLocked() {
    static const std::regex r(R"rgx(Your account has been locked)rgx");
    return r;
}
const std::regex& ReWindowFocus() {
    static const std::regex r(R"rgx(\[WINDOW\] (Lost focus|Focus gained))rgx");
    return r;
}
const std::regex& RePlayerSlain() {
    static const std::regex r(R"rgx(:\s*([A-Za-z0-9_]+)\s+has been slain\.)rgx");
    return r;
}
const std::regex& RePlayerSuicide() {
    static const std::regex r(R"rgx(:\s*([A-Za-z0-9_]+)\s+has committed suicide\.)rgx");
    return r;
}
const std::regex& RePlayerJoinedArea() {
    static const std::regex r(R"rgx(:\s*([A-Za-z0-9_]+)\s+has joined the area\.)rgx");
    return r;
}
const std::regex& RePlayerLeftArea() {
    static const std::regex r(R"rgx(:\s*([A-Za-z0-9_]+)\s+has left the area\.)rgx");
    return r;
}
const std::regex& RePartyChat() {
    static const std::regex r(R"rgx(%\s*([^:]+):\s*(.*))rgx");
    return r;
}
const std::regex& ReGuildChat() {
    static const std::regex r(R"rgx(&\s*([^:]+):\s*(.*))rgx");
    return r;
}
const std::regex& ReTradeAccepted() {
    static const std::regex r(R"rgx(:\s*Trade accepted\.)rgx");
    return r;
}
const std::regex& ReTradeCancelled() {
    static const std::regex r(R"rgx(:\s*Trade cancelled\.)rgx");
    return r;
}
const std::regex& ReInstanceConnected() {
    static const std::regex r(R"rgx(Connecting to instance server at ([0-9\.]+:\d+))rgx");
    return r;
}
const std::regex& ReInstanceConnectTime() {
    static const std::regex r(R"rgx(Connect time to instance server was (\d+)ms)rgx");
    return r;
}
const std::regex& ReMonstersRemain() {
    static const std::regex r(R"rgx((?:More than 50 monsters remain|(?:There are\s+)?(\d+)\s+monsters?\s+remain))rgx", std::regex::icase);
    return r;
}

bool IsIgnorableSceneName(const std::string& name) {
    if (name.empty() || name == "(null)" || name == "(unknown)") return true;
    if (name == "Act 1" || name == "Act 2" || name == "Act 3" || name == "Act 4" || name == "Atlas") {
        return true;
    }
    if (name.size() >= 4 && name.compare(0, 4, "[DNT") == 0) return true;
    return false;
}

std::string DirOfPath(const std::string& p) {
    const auto pos = p.find_last_of("\\/");
    if (pos == std::string::npos) return ".";
    return p.substr(0, pos);
}

std::string BaseNameOfPath(const std::string& p) {
    const auto pos = p.find_last_of("\\/");
    return pos == std::string::npos ? p : p.substr(pos + 1);
}

bool FileExistsA(const std::string& p) {
    DWORD attribs = GetFileAttributesA(p.c_str());
    return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
}

std::string DiscoverLogFromRunningClient() {
    uint32_t pid = FindPoeProcessId();
    if (pid == 0) return {};
    HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
    if (!hProc) return {};
    std::string selected;
    wchar_t exePathW[MAX_PATH] = {};
    DWORD size = MAX_PATH;
    if (QueryFullProcessImageNameW(hProc, 0, exePathW, &size)) {
        std::wstring ws(exePathW);
        auto pos = ws.find_last_of(L"\\/");
        if (pos != std::wstring::npos) {
            std::wstring dirW = ws.substr(0, pos);
            std::wstring clientW = dirW + L"\\logs\\Client.txt";
            std::wstring latestW = dirW + L"\\logs\\LatestClient.txt";
            DWORD clientAttr = GetFileAttributesW(clientW.c_str());
            bool clientExists = (clientAttr != INVALID_FILE_ATTRIBUTES && !(clientAttr & FILE_ATTRIBUTE_DIRECTORY));
            DWORD latestAttr = GetFileAttributesW(latestW.c_str());
            bool latestExists = (latestAttr != INVALID_FILE_ATTRIBUTES && !(latestAttr & FILE_ATTRIBUTE_DIRECTORY));
            std::wstring selectedW;
            if (clientExists) selectedW = clientW;
            else if (latestExists) selectedW = latestW;
            else selectedW = clientW;
            char pathBuf[MAX_PATH * 2] = {};
            WideCharToMultiByte(CP_UTF8, 0, selectedW.c_str(), -1, pathBuf, sizeof(pathBuf), nullptr, nullptr);
            selected = pathBuf;
        }
    }
    CloseHandle(hProc);
    return selected;
}

} // namespace

std::string LogSensor::ResolveLogPath(const std::string& requested) {
    const std::string base = BaseNameOfPath(requested);
    const bool stockName = (base == "Client.txt" || base == "LatestClient.txt"
        || requested == kDefaultLogPath);
    const std::string dir = DirOfPath(requested);
    const std::string client = dir + "\\Client.txt";
    const std::string latest = dir + "\\LatestClient.txt";

    if (stockName && FileExistsA(client)) return client;
    if (FileExistsA(requested)) return requested;
    if (stockName && FileExistsA(latest)) return latest;
    if (stockName) {
        std::string discovered = DiscoverLogFromRunningClient();
        if (!discovered.empty() && FileExistsA(discovered)) return discovered;
        if (!discovered.empty()) return discovered;
    }
    return requested;
}

LogSensor::LogSensor(std::string logPath) : m_logPath(ResolveLogPath(std::move(logPath))) {
}

LogSensor::~LogSensor() {
    Stop();
}

bool LogSensor::Start() {
    if (m_running.load(std::memory_order_acquire)) return true;

    m_running.store(true, std::memory_order_release);
    try {
        m_worker = std::thread(&LogSensor::WorkerLoop, this);
    } catch (...) {
        m_running.store(false, std::memory_order_release);
        return false;
    }
    std::cout << "[LogSensor] Bat dau tail log: " << m_logPath << std::endl;
    return true;
}

void LogSensor::Stop() {
    if (!m_running.exchange(false, std::memory_order_acq_rel)) return;

    if (m_worker.joinable()) m_worker.join();

    // TASK-OPT-06: Đóng file và clear state để tránh rò rỉ file handle (ERROR_SHARING_VIOLATION 32)
    if (m_file.is_open()) {
        m_file.close();
        m_file.clear();
    }
    m_fileOpen.store(false, std::memory_order_release);
    m_fileOffset = 0;
    m_pendingLine.clear();

    std::lock_guard<std::mutex> lock(m_mutex);
    m_events.clear();
}

bool LogSensor::PollEvent(GameEvent& outEvent) {
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_events.empty()) return false;
    outEvent = std::move(m_events.front());
    m_events.pop_front();
    return true;
}

size_t LogSensor::PendingEventCount() const {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_events.size();
}

void LogSensor::PushEvent(const GameEvent& ev) {
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_events.size() >= kMaxQueuedEvents) {
        m_events.pop_front(); // Tràn: bỏ sự kiện cũ nhất
    }
    m_events.push_back(ev);
}

void LogSensor::AddFriend(const std::string& name) {
    if (name.empty()) return;
    std::lock_guard<std::mutex> lock(m_friendsMutex);
    std::string lower = ToLower(name);
    for (const auto& f : m_friendsWhitelist) {
        if (ToLower(f) == lower) return;
    }
    m_friendsWhitelist.push_back(name);
}

void LogSensor::RemoveFriend(const std::string& name) {
    std::lock_guard<std::mutex> lock(m_friendsMutex);
    std::string lower = ToLower(name);
    std::erase_if(m_friendsWhitelist, [&](const std::string& f) {
        return ToLower(f) == lower;
    });
}

void LogSensor::ClearFriends() {
    std::lock_guard<std::mutex> lock(m_friendsMutex);
    m_friendsWhitelist.clear();
}

bool LogSensor::IsFriend(const std::string& name) const {
    if (name.empty()) return false;
    std::lock_guard<std::mutex> lock(m_friendsMutex);
    std::string lower = ToLower(name);
    for (const auto& f : m_friendsWhitelist) {
        if (ToLower(f) == lower) return true;
    }
    return false;
}

void LogSensor::LoadFriendsList(const std::string& filePath) {
    std::ifstream file(filePath);
    if (!file.is_open()) return;
    std::string line;
    while (std::getline(file, line)) {
        while (!line.empty() && (line.back() == '\r' || line.back() == '\n' || line.back() == ' ' || line.back() == '\t')) {
            line.pop_back();
        }
        size_t start = line.find_first_not_of(" \t");
        if (start == std::string::npos) continue;
        std::string name = line.substr(start);
        if (name.empty() || name[0] == '#') continue;
        AddFriend(name);
    }
}

void LogSensor::ReplayLatestPlayableZoneFromTail() {
    m_file.clear();
    m_file.seekg(0, std::ios::end);
    const auto endPos = m_file.tellg();
    if (endPos < 0) {
        m_fileOffset = 0;
        return;
    }
    const auto fileSize = static_cast<uint64_t>(endPos);
    if (fileSize == 0) {
        m_fileOffset = 0;
        return;
    }

    constexpr uint64_t kWindow = 2ull * 1024ull * 1024ull;
    const uint64_t start = fileSize > kWindow ? fileSize - kWindow : 0;
    m_file.seekg(static_cast<std::streamoff>(start), std::ios::beg);
    std::string chunk(static_cast<size_t>(fileSize - start), '\0');
    m_file.read(chunk.data(), static_cast<std::streamsize>(chunk.size()));
    if (start > 0) {
        const auto nl = chunk.find('\n');
        if (nl != std::string::npos) {
            chunk.erase(0, nl + 1);
        }
    }

    std::string lastScene;
    std::string lastCode;
    int lastLevel = 0;
    uint64_t lastSeed = 0;
    bool haveGen = false;
    std::string lastLoadName;
    float lastLoadDur = 0.0f;

    size_t pos = 0;
    while (pos < chunk.size()) {
        const auto nl = chunk.find('\n', pos);
        std::string line = chunk.substr(pos, nl == std::string::npos ? std::string::npos : nl - pos);
        pos = (nl == std::string::npos) ? chunk.size() : nl + 1;
        if (!line.empty() && line.back() == '\r') line.pop_back();
        if (line.empty()) continue;

        std::smatch m;
        if (std::regex_search(line, m, ReAreaGenerated())) {
            try {
                lastLevel = std::stoi(m[1].str());
                lastCode = m[2].str();
                lastSeed = std::stoull(m[3].str());
                haveGen = true;
            } catch (...) {
            }
            continue;
        }
        if (std::regex_search(line, m, ReSceneChanged())) {
            const std::string name = m[1].str();
            if (!IsIgnorableSceneName(name)) lastScene = name;
            continue;
        }
        if (std::regex_search(line, m, ReLoadingFinished())) {
            const std::string name = m[1].str();
            if (!IsIgnorableSceneName(name)) {
                lastLoadName = name;
                try {
                    lastLoadDur = std::stof(m[2].str());
                } catch (...) {
                    lastLoadDur = 0.0f;
                }
            }
        }
    }

    if (!lastScene.empty()) {
        if (haveGen) {
            GameEvent gen{};
            gen.timestampMs = NowMs();
            gen.type = GameEventType::AreaGenerated;
            gen.areaLevel = lastLevel;
            gen.text = lastCode;
            gen.areaSeed = lastSeed;
            PushEvent(gen);
        }
        GameEvent scene{};
        scene.timestampMs = NowMs();
        scene.type = GameEventType::SceneChanged;
        scene.text = lastScene;
        PushEvent(scene);
        if (!lastLoadName.empty()) {
            GameEvent load{};
            load.timestampMs = NowMs();
            load.type = GameEventType::LoadingFinished;
            load.text = lastLoadName;
            load.loadDurationSec = lastLoadDur;
            PushEvent(load);
        }
    }

    m_fileOffset = fileSize;
    m_file.clear();
    m_file.seekg(0, std::ios::end);
}

void LogSensor::WorkerLoop() {
    while (m_running.load(std::memory_order_acquire)) {
        // Mở file nếu chưa mở (chờ file xuất hiện nếu game chưa chạy)
        if (!m_file.is_open()) {
            m_file.open(m_logPath, std::ios::binary);
            if (!m_file) {
                m_fileOpen.store(false, std::memory_order_release);
                std::this_thread::sleep_for(std::chrono::milliseconds(250));
                continue;
            }
            // Seed SCENE/Generating gần nhất từ đuôi file, rồi đặt offset = EOF.
            // Nếu chỉ seek END, Core/tool mở giữa map (Hive Fortress) sẽ để
            // currentAreaName rỗng vĩnh viễn cho tới khi đổi zone lần nữa.
            ReplayLatestPlayableZoneFromTail();
            m_fileOpen.store(true, std::memory_order_release);
        }

        // Kiểm tra file có bị xoá/đổi phiên (kích thước nhỏ hơn vị trí đã đọc)
        m_file.seekg(0, std::ios::end);
        const auto fileSize = static_cast<uint64_t>(m_file.tellg());
        if (fileSize < m_fileOffset) {
            // Phiên chơi mới: file bị xoá lại từ đầu
            m_file.close();
            m_file.clear();
            m_fileOpen.store(false, std::memory_order_release);
            m_fileOffset = 0;
            m_pendingLine.clear();
            continue;
        }

        // Đọc toàn bộ dữ liệu mới
        const auto newSize = static_cast<uint64_t>(fileSize);
        if (newSize > m_fileOffset) {
            m_file.seekg(static_cast<std::streamoff>(m_fileOffset), std::ios::beg);
            std::string chunk(static_cast<size_t>(newSize - m_fileOffset), '\0');
            m_file.read(&chunk[0], static_cast<std::streamsize>(chunk.size()));
            m_fileOffset = newSize;

            // Ghép với dòng dở dang, tách theo '\n'
            m_pendingLine += chunk;
            size_t start = 0;
            size_t nl;
            while ((nl = m_pendingLine.find('\n', start)) != std::string::npos) {
                std::string line = m_pendingLine.substr(start, nl - start);
                if (!line.empty() && line.back() == '\r') line.pop_back();
                if (!line.empty()) ProcessLine(line);
                start = nl + 1;
            }
            m_pendingLine.erase(0, start);
        } else {
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }

    if (m_file.is_open()) {
        m_file.close();
        m_file.clear();
    }
    m_fileOpen.store(false, std::memory_order_release);
}

void LogSensor::ProcessLine(const std::string& line) {
    std::smatch m;
    GameEvent ev;
    ev.timestampMs = NowMs();

    // 1. Tạo instance mới (AreaCode + Seed - nền móng cho NavMesh G3)
    if (std::regex_search(line, m, ReAreaGenerated())) {
        ev.type = GameEventType::AreaGenerated;
        ev.areaLevel = std::stoi(m[1].str());
        ev.text = m[2].str();
        ev.areaSeed = std::stoull(m[3].str());
        PushEvent(ev);
        return;
    }

    // 2. Chuyển khu vực
    if (std::regex_search(line, m, ReSceneChanged())) {
        ev.type = GameEventType::SceneChanged;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 3. Kết thúc tải màn
    if (std::regex_search(line, m, ReLoadingFinished())) {
        ev.type = GameEventType::LoadingFinished;
        ev.text = m[1].str();
        ev.loadDurationSec = std::stof(m[2].str());
        PushEvent(ev);
        return;
    }

    // 4. Whisper (báo động cho SafetySupervisorAgent)
    if (std::regex_search(line, m, ReWhisper())) {
        ev.type = GameEventType::WhisperReceived;
        ev.text = m[1].str();
        ev.text2 = m[2].str();
        PushEvent(ev);
        return;
    }

    // 5. Mất kết nối bất thường
    if (std::regex_search(line, m, ReAbnormalDisconnect())) {
        ev.type = GameEventType::AbnormalDisconnect;
        ev.text = m[1].matched ? m[1].str() : m[2].str();
        PushEvent(ev);
        return;
    }

    // 6. Tài khoản bị khóa (báo động đỏ - Panic toàn hệ thống)
    if (std::regex_search(line, ReAccountLocked())) {
        ev.type = GameEventType::AccountLocked;
        ev.text = "ACCOUNT LOCKED";
        PushEvent(ev);
        return;
    }

    // 7. Mất/nhận focus cửa sổ (tạm dừng bot khi Alt-Tab)
    if (std::regex_search(line, m, ReWindowFocus())) {
        ev.type = GameEventType::WindowFocus;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 8. Tin nhắn kênh party (%)
    if (std::regex_search(line, m, RePartyChat())) {
        ev.type = GameEventType::PartyChat;
        ev.text = m[1].str();
        ev.text2 = m[2].str();
        PushEvent(ev);
        return;
    }

    // 9. Tin nhắn kênh guild (&)
    if (std::regex_search(line, m, ReGuildChat())) {
        ev.type = GameEventType::GuildChat;
        ev.text = m[1].str();
        ev.text2 = m[2].str();
        PushEvent(ev);
        return;
    }

    // 10. Giao dịch thành công
    if (std::regex_search(line, ReTradeAccepted())) {
        ev.type = GameEventType::TradeAccepted;
        ev.text = "Trade accepted";
        PushEvent(ev);
        return;
    }

    // 11. Hủy giao dịch
    if (std::regex_search(line, ReTradeCancelled())) {
        ev.type = GameEventType::TradeCancelled;
        ev.text = "Trade cancelled";
        PushEvent(ev);
        return;
    }

    // 12. Người chơi tử vong
    if (std::regex_search(line, m, RePlayerSlain())) {
        ev.type = GameEventType::PlayerSlain;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 13. Người chơi tự sát
    if (std::regex_search(line, m, RePlayerSuicide())) {
        ev.type = GameEventType::PlayerSuicide;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 14. Người chơi bước vào khu vực (Hideout/Map)
    if (std::regex_search(line, m, RePlayerJoinedArea())) {
        ev.type = GameEventType::PlayerJoinedArea;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 15. Người chơi rời khu vực
    if (std::regex_search(line, m, RePlayerLeftArea())) {
        ev.type = GameEventType::PlayerLeftArea;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 16. Kết nối Instance Server
    if (std::regex_search(line, m, ReInstanceConnected())) {
        ev.type = GameEventType::InstanceConnected;
        ev.text = m[1].str();
        PushEvent(ev);
        return;
    }

    // 17. Đo thời gian kết nối instance (Latency ms)
    if (std::regex_search(line, m, ReInstanceConnectTime())) {
        ev.type = GameEventType::InstanceConnected;
        ev.loadDurationSec = std::stof(m[1].str());
        PushEvent(ev);
        return;
    }

    // 18. Tin nhắn kênh trade (ưu tiên thấp nhất)
    if (std::regex_search(line, m, ReTradeMessage())) {
        ev.type = GameEventType::TradeMessage;
        ev.text = m[1].str();
        ev.text2 = m[2].str();
        PushEvent(ev);
        return;
    }

    // 19. Thông báo quái vật còn lại trong khu vực (/remaining)
    if (std::regex_search(line, m, ReMonstersRemain())) {
        ev.type = GameEventType::MonstersRemain;
        if (m[1].matched) {
            ev.areaLevel = std::stoi(m[1].str());
        } else {
            ev.areaLevel = 50; // "More than 50 monsters remain"
        }
        ev.text = line;
        PushEvent(ev);
        return;
    }
}
