"""
JXHelper - Dev Supervisor & Shadow Binary Hot-Swap Engine
=========================================================
Kế thừa kiến trúc từ AutoPOE2 (INV-SUPERVISOR-SHADOW-EXEC & INV-SHM-TELEMETRY-SSOT).
1. Shadow Binary: Copy bản build sang runtime/ để chạy test không bao giờ bị khóa file khi dotnet build.
2. Hot-Swap: Tự động phát hiện khi dev sửa code & build mới, reload mượt mà.
3. Shared Memory Inspector: Đọc trực tiếp trạng thái RAM live (0 I/O) từ Local\\JX_Helper_SHM_v1.
4. Telemetry Stream: Đọc dữ liệu trực tiếp & dữ liệu quá khứ từ logs/telemetry.jsonl.
"""

import os
import sys
import time

if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")
        sys.stderr.reconfigure(encoding="utf-8", errors="replace")
    except Exception:
        pass

import json
import shutil
import mmap
import ctypes
import hashlib
import argparse
import subprocess
from pathlib import Path

WORKSPACE_ROOT = Path(__file__).resolve().parent.parent
SRC_BIN_DIR = WORKSPACE_ROOT / "src" / "JXHelper" / "bin" / "Debug" / "net8.0-windows"
SRC_EXE = SRC_BIN_DIR / "JXHelper.exe"
RUNTIME_DIR = WORKSPACE_ROOT / "runtime"
RUNTIME_EXE = RUNTIME_DIR / "JXHelper_Runtime.exe"
LOGS_DIR = WORKSPACE_ROOT / "logs"
JSONL_LOG = LOGS_DIR / "telemetry.jsonl"
ACTION_LOG = LOGS_DIR / "actions.log"

SHM_NAME = "Local\\JX_Helper_SHM_v1"
SHM_MAGIC = 0x4A583031  # "JX01"

STATE_NAMES = {
    0: "Idle (Đang chờ)",
    1: "Running (Đang canh chu kỳ)",
    2: "ReturningToTown (Đang Về Thành)",
    3: "WaitingMapLoad (Đang nạp map)",
    4: "OpeningBag (Đang mở F4)",
    5: "ClickingRepairItem (Đang dùng Túi Đồ Nghề)",
    6: "ClosingBag (Đang đóng F4)",
    7: "FinishedCycle (Hoàn tất chu kỳ)",
    8: "Paused (Tạm dừng)",
    9: "Error (Gặp lỗi)"
}

class JxTelemetryData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("magic", ctypes.c_uint32),
        ("version", ctypes.c_uint32),
        ("snapshot_id", ctypes.c_uint64),
        ("timestamp_ms", ctypes.c_uint64),
        ("state", ctypes.c_int32),
        ("target_hwnd", ctypes.c_int32),
        ("target_pid", ctypes.c_int32),
        ("completed_cycles", ctypes.c_int32),
        ("next_countdown_sec", ctypes.c_int32),
        ("repair_item_x", ctypes.c_int32),
        ("repair_item_y", ctypes.c_int32),
        ("travel_item_x", ctypes.c_int32),
        ("travel_item_y", ctypes.c_int32),
        ("last_action", ctypes.c_char * 64),
    ]


def get_file_hash(filepath: Path) -> str:
    if not filepath.exists():
        return ""
    h = hashlib.sha256()
    with open(filepath, "rb") as f:
        while chunk := f.read(65536):
            h.update(chunk)
    return h.hexdigest()


def prepare_shadow_binary() -> bool:
    """Sao chép toàn bộ thư mục build sang runtime/ để giải phóng binary gốc."""
    if not SRC_EXE.exists():
        print(f"[LỖI] Chưa tìm thấy file build gốc tại: {SRC_EXE}")
        print("Vui lòng chạy 'dotnet build src/JXHelper/JXHelper.csproj' trước.")
        return False

    RUNTIME_DIR.mkdir(parents=True, exist_ok=True)

    # Copy các file liên quan (.dll, .json, .exe)
    for src_file in SRC_BIN_DIR.glob("*.*"):
        if src_file.suffix.lower() in [".dll", ".json", ".pdb", ".exe"]:
            shutil.copy2(src_file, RUNTIME_DIR / src_file.name)
            # Tạo bản copy tương ứng mang tên JXHelper_Runtime.* cho runtimeconfig, deps, dll, exe, pdb
            if src_file.name.startswith("JXHelper."):
                runtime_target = src_file.name.replace("JXHelper.", "JXHelper_Runtime.")
                shutil.copy2(src_file, RUNTIME_DIR / runtime_target)

    print(f"[SHADOW] Đã sao chép thành công Shadow Binary sang: {RUNTIME_EXE}")
    return True


def read_shm_telemetry() -> dict | None:
    """Đọc dữ liệu thời gian thực trực tiếp từ RAM Shared Memory (Zero I/O)."""
    struct_size = ctypes.sizeof(JxTelemetryData)
    try:
        # Mở Shared Memory do C# tạo ra
        shm = mmap.mmap(-1, struct_size, SHM_NAME, access=mmap.ACCESS_READ)
        raw_bytes = shm.read(struct_size)
        shm.close()

        data = JxTelemetryData.from_buffer_copy(raw_bytes)
        if data.magic != SHM_MAGIC:
            return None

        last_action_str = data.last_action.decode("utf-8", errors="ignore").rstrip("\x00")
        return {
            "snapshot_id": data.snapshot_id,
            "timestamp_ms": data.timestamp_ms,
            "state_id": data.state,
            "state_name": STATE_NAMES.get(data.state, f"State_{data.state}"),
            "target_hwnd": hex(data.target_hwnd),
            "target_pid": data.target_pid,
            "completed_cycles": data.completed_cycles,
            "next_countdown_sec": data.next_countdown_sec,
            "repair_pos": (data.repair_item_x, data.repair_item_y),
            "travel_pos": (data.travel_item_x, data.travel_item_y),
            "last_action": last_action_str
        }
    except Exception:
        return None


def print_shm_status(loop: bool = False):
    """Hiển thị trạng thái RAM trực tiếp."""
    print("=" * 65)
    print(" JXHELPER SHARED MEMORY (RAM) LIVE TELEMETRY")
    print("=" * 65)

    try:
        while True:
            telem = read_shm_telemetry()
            if not telem:
                print("\r[OFFLINE] JXHelper chưa bật hoặc chưa kết nối Shared Memory...", end="", flush=True)
            else:
                now_ms = int(time.time() * 1000)
                delay_ms = max(0, now_ms - telem["timestamp_ms"])
                status_line = (
                    f"Tick: #{telem['snapshot_id']:<5} | "
                    f"Trạng thái: {telem['state_name']:<25} | "
                    f"PID: {telem['target_pid']} | "
                    f"Đếm ngược: {telem['next_countdown_sec']:>3}s | "
                    f"Chu kỳ: {telem['completed_cycles']} | "
                    f"Lag: {delay_ms}ms"
                )
                if loop:
                    print(f"\r{status_line} | Lệnh: {telem['last_action'][:20]:<20}", end="", flush=True)
                else:
                    print(status_line)
                    print(f"Hành động gần nhất: {telem['last_action']}")
                    print(f"Tọa độ Túi Đồ Nghề: {telem['repair_pos']} | Tọa độ Thần Hành Phù: {telem['travel_pos']}")
                    break

            if not loop:
                break
            time.sleep(0.5)
    except KeyboardInterrupt:
        print("\n[DỪNG] Đã thoát xem RAM live.")


def tail_logs(num_lines: int = 20):
    """Xem dữ liệu quá khứ và theo dõi live từ telemetry.jsonl."""
    if not JSONL_LOG.exists():
        print(f"[THÔNG BÁO] Chưa có dữ liệu log tại {JSONL_LOG}")
        return

    print("=" * 65)
    print(f" THEO DÕI LIVE NHẬT KÝ QUÁ KHỨ & HIỆN TẠI ({JSONL_LOG.name})")
    print("=" * 65)

    with open(JSONL_LOG, "r", encoding="utf-8", errors="ignore") as f:
        lines = f.readlines()
        for line in lines[-num_lines:]:
            try:
                item = json.loads(line.strip())
                print(f"[{item.get('Timestamp', '')[:19]}] [{item.get('State')}] (PID:{item.get('TargetPid')}) {item.get('EventName')}: {item.get('Details')}")
            except Exception:
                print(line.strip())

        try:
            while True:
                line = f.readline()
                if line:
                    try:
                        item = json.loads(line.strip())
                        print(f"[{item.get('Timestamp', '')[:19]}] [{item.get('State')}] (PID:{item.get('TargetPid')}) {item.get('EventName')}: {item.get('Details')}")
                    except Exception:
                        print(line.strip())
                else:
                    time.sleep(0.5)
        except KeyboardInterrupt:
            print("\n[DỪNG] Đã thoát theo dõi log.")


def run_supervisor(auto_restart: bool = True):
    """Vận hành vòng lặp Shadow Binary & Hot-Swap."""
    if not prepare_shadow_binary():
        return

    current_hash = get_file_hash(SRC_EXE)
    print(f"[GIÁM SÁT] Khởi chạy Shadow Binary: {RUNTIME_EXE}")
    proc = subprocess.Popen([str(RUNTIME_EXE)], cwd=str(RUNTIME_DIR))

    print(f"[HOTSWAP SẴN SÀNG] PID: {proc.pid}")
    print("[MẸO] Bạn có thể sửa code và chạy 'dotnet build' bất kỳ lúc nào!")
    print("[MẸO] Khi file gốc thay đổi, Supervisor sẽ tự động Hot-Swap sang bản mới.")

    try:
        while True:
            time.sleep(1.0)
            # Kiểm tra tiến trình runtime còn sống không
            if proc.poll() is not None:
                print(f"[THÔNG BÁO] Bản test đã tắt (Exit Code: {proc.returncode}).")
                break

            # Kiểm tra xem dev có vừa build bản mới không
            if SRC_EXE.exists():
                new_hash = get_file_hash(SRC_EXE)
                if new_hash and new_hash != current_hash:
                    print("\n" + "=" * 65)
                    print("[HOT-SWAP] PHÁT HIỆN BẢN BUILD MỚI! Bắt đầu chuyển giao an toàn...")
                    print("=" * 65)

                    # Tắt tiến trình cũ an toàn
                    proc.terminate()
                    try:
                        proc.wait(timeout=3)
                    except subprocess.TimeoutExpired:
                        proc.kill()

                    # Cập nhật Shadow Binary
                    prepare_shadow_binary()
                    current_hash = new_hash

                    # Khởi động lại
                    proc = subprocess.Popen([str(RUNTIME_EXE)], cwd=str(RUNTIME_DIR))
                    print(f"[HOT-SWAP HOÀN TẤT] Tiến trình mới đã khởi chạy (PID: {proc.pid})!\n")

    except KeyboardInterrupt:
        print("\n[DỪNG] Đang dừng tiến trình Shadow Binary...")
        if proc.poll() is None:
            proc.terminate()
            try:
                proc.wait(timeout=2)
            except subprocess.TimeoutExpired:
                proc.kill()
        print("[HOÀN TẤT] Supervisor đã dừng an toàn.")


def main():
    parser = argparse.ArgumentParser(description="JXHelper Dev Supervisor & Hot-Swap Engine")
    parser.add_argument("--run", action="store_true", help="Khởi chạy Tool ở chế độ Shadow Binary & Hot-Swap")
    parser.add_argument("--shm", action="store_true", help="Đọc trực tiếp dữ liệu RAM (Shared Memory)")
    parser.add_argument("--watch-shm", action="store_true", help="Theo dõi liên tục RAM Live 0 I/O")
    parser.add_argument("--tail", action="store_true", help="Theo dõi live stream dữ liệu lịch sử & hiện tại (logs/telemetry.jsonl)")
    parser.add_argument("--build-swap", action="store_true", help="Build lại code C# và hot-swap vào runtime")

    args = parser.parse_args()

    if args.shm:
        print_shm_status(loop=False)
    elif args.watch_shm:
        print_shm_status(loop=True)
    elif args.tail:
        tail_logs()
    elif args.build_swap:
        print("[BUILD] Đang biên dịch C# .NET 8...")
        res = subprocess.run(["dotnet", "build", "src/JXHelper/JXHelper.csproj"], cwd=str(WORKSPACE_ROOT))
        if res.returncode == 0:
            print("[BUILD] Biên dịch thành công 0 lỗi! Chuẩn bị Hot-Swap...")
            prepare_shadow_binary()
        else:
            print("[LỖI] Biên dịch thất bại!")
    elif args.run:
        run_supervisor()
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
