// ==========================================================
// AutoPOE2 Core Engine - Unit Tests
// Domain 3: Telegraph Evasion Arbiter (Test 101)
// (Sư đoàn 1: Proactive Boss Slam & AOE Evasion)
// ==========================================================

#include "test_harness.hpp"
#include "combat/telegraph_evasion_arbiter.hpp"
#include "navigation/terrain_grid.hpp"

#include <chrono>
#include <cmath>
#include <iostream>

void TestTelegraphEvasionArbiter() {
    std::cout << "[Test 101] Telegraph Evasion Arbiter & Proactive AOE Dodging..." << std::endl;

    combat::TelegraphArbiterConfig config{};
    config.defaultSlamRadius = 150.0f;
    config.defaultConeRadius = 200.0f;
    config.defaultConeAngleRad = 0.7854f; // 45 độ (pi / 4)
    config.defaultRectLength = 240.0f;
    config.defaultRectWidth = 60.0f;
    config.rollUrgencyWindowMs = 350.0f;
    config.rollCooldownMs = 600;
    config.safetyMargin = 10.0f;

    combat::TelegraphEvasionArbiter arbiter(config);

    // ----------------------------------------------------------
    // 1. Sub-test 1: Circle Slam (Dập sàn hình tròn)
    // ----------------------------------------------------------
    {
        combat::TelegraphThreat slam{};
        slam.sourceEntityId = 999;
        slam.shape = combat::TelegraphShape::Circle;
        slam.originX = 500.0f;
        slam.originY = 500.0f;
        slam.radius = 150.0f;
        slam.timeRemainingMs = 250.0f;

        Vector2D playerInside{ 550.0f, 550.0f }; // dx=50, dy=50, dist=70.71u
        CHECK(combat::TelegraphEvasionArbiter::IsPointInDangerZone(playerInside, slam),
              "Nguoi choi phai bi phat hien nam trong Circle Slam");

        float dirX = 0.0f, dirY = 0.0f, dist = 0.0f;
        bool inDanger = arbiter.EvaluateSingleThreat(playerInside, slam, dirX, dirY, dist);
        CHECK(inDanger, "EvaluateSingleThreat phai tra ve true cho Circle Slam");

        // Vector thoát hiểm phải ly tâm: (1/sqrt(2), 1/sqrt(2)) ~ (0.7071, 0.7071)
        CHECK(std::fabs(dirX - 0.7071f) < 0.01f, "Huong thoat Circle Slam X phai ly tam");
        CHECK(std::fabs(dirY - 0.7071f) < 0.01f, "Huong thoat Circle Slam Y phai ly tam");

        // Khoang cach thoat phai bang (150 - 70.71) + 10 = 89.29u
        CHECK(std::fabs(dist - 89.29f) < 1.0f, "Khoang cach thoat Circle Slam phai chinh xac");

        Vector2D playerOutside{ 700.0f, 700.0f };
        CHECK(!combat::TelegraphEvasionArbiter::IsPointInDangerZone(playerOutside, slam),
              "Nguoi choi o ngoai ban kinh phai an toan");
    }

    // ----------------------------------------------------------
    // 2. Sub-test 2: Frontal Cone (Đòn quét nón phía trước)
    // ----------------------------------------------------------
    {
        combat::TelegraphThreat cone{};
        cone.sourceEntityId = 888;
        cone.shape = combat::TelegraphShape::FrontalCone;
        cone.originX = 100.0f;
        cone.originY = 100.0f;
        cone.radius = 200.0f;
        cone.widthOrHalfAngle = 0.7854f; // 45 độ
        cone.directionRad = 0.0f; // Boss quay ve huong Dong (+X)

        Vector2D playerInCone{ 180.0f, 130.0f }; // dx=80, dy=30, angle = 0.358 rad (< 0.785 rad)
        CHECK(combat::TelegraphEvasionArbiter::IsPointInDangerZone(playerInCone, cone),
              "Nguoi choi phai bi phat hien nam trong Frontal Cone");

        float dirX = 0.0f, dirY = 0.0f, dist = 0.0f;
        bool inDanger = arbiter.EvaluateSingleThreat(playerInCone, cone, dirX, dirY, dist);
        CHECK(inDanger, "EvaluateSingleThreat phai tra ve true cho Frontal Cone");

        // Huong thoat phai tiep tuyen lateral (ngan hon chay nguoc)
        CHECK(dist > 0.0f && dist < 100.0f, "Khoang cach thoat ngang Frontal Cone phai nho hon thoat huong tam");

        Vector2D playerBehind{ 50.0f, 100.0f }; // Dung phia sau Boss
        CHECK(!combat::TelegraphEvasionArbiter::IsPointInDangerZone(playerBehind, cone),
              "Nguoi choi o phia sau Boss phai hoan toan an toan khoi Frontal Cone");
    }

    // ----------------------------------------------------------
    // 3. Sub-test 3: Rectangle Beam (Đòn quét thẳng hình chữ nhật)
    // ----------------------------------------------------------
    {
        combat::TelegraphThreat rect{};
        rect.sourceEntityId = 777;
        rect.shape = combat::TelegraphShape::Rectangle;
        rect.originX = 0.0f;
        rect.originY = 0.0f;
        rect.radius = 240.0f; // Chieu dai L
        rect.widthOrHalfAngle = 60.0f; // Bề rong W
        rect.directionRad = 0.0f; // Ban doc theo truc +X

        Vector2D playerInBeam{ 120.0f, 18.0f }; // Giua tam tia, lech phai 18u (< 30u)
        CHECK(combat::TelegraphEvasionArbiter::IsPointInDangerZone(playerInBeam, rect),
              "Nguoi choi phai bi phat hien nam trong Rectangle Beam");

        float dirX = 0.0f, dirY = 0.0f, dist = 0.0f;
        bool inDanger = arbiter.EvaluateSingleThreat(playerInBeam, rect, dirX, dirY, dist);
        CHECK(inDanger, "EvaluateSingleThreat phai tra ve true cho Rectangle Beam");

        // Huong thoat ngang ngan nhat la theo truc +Y (dirY = 1.0)
        CHECK(std::fabs(dirX) < 0.001f && std::fabs(dirY - 1.0f) < 0.001f,
              "Huong thoat Rectangle Beam phai vuong goc voi truc ban (+Y)");
        // Khoang cach thoat: (30 - 18) + 10 = 22u
        CHECK(std::fabs(dist - 22.0f) < 0.5f, "Khoang cach thoat ngang Rectangle Beam phai bang 22u");
    }

    // ----------------------------------------------------------
    // 4. Sub-test 4: TelemetryPacket 120Hz & Quyết định Iframe Roll vs Stutter-step
    // ----------------------------------------------------------
    {
        TelemetryPacket packet{};
        packet.player.posX = 300.0f;
        packet.player.posY = 300.0f;
        packet.player.maxHP = 1; // Monk CI
        packet.player.currentHP = 1;
        packet.player.maxES = 4000;
        packet.player.currentES = 4000;

        packet.entityCount = 10;
        auto& boss = packet.entities[0];
        boss.id = 505;
        boss.type = 1;
        boss.rarity = 3; // Boss
        boss.posX = 340.0f;
        boss.posY = 300.0f;
        boss.currentHP = 50000;
        boss.maxHP = 50000;
        boss.extraFlags = 1; // Boss flag
        boss.currentAnimationId = 105; // Circle Slam (timeRemaining = 320ms <= 350ms urgency)

        uint64_t nowMs = 50000;

        // Tick 1: Gap don slam gap -> IframeRoll (Spacebar)
        auto decision1 = arbiter.EvaluateThreats(packet, nowMs);
        CHECK(decision1.action == combat::EvasionActionKind::IframeRoll,
              "Don Slam gap duoi 350ms phai kich hoat Iframe Roll (Spacebar)");
        CHECK(decision1.threatEntityId == 505, "Moi de doa phai den tu Boss 505");
        CHECK(arbiter.IsRollOnCooldown(nowMs + 100), "Roll phai vao cooldown sau khi thuc thi");

        // Tick 2: 100ms sau (van trong cooldown 600ms) -> Chuyen sang Stutter-step
        auto decision2 = arbiter.EvaluateThreats(packet, nowMs + 100);
        CHECK(decision2.action == combat::EvasionActionKind::StutterStep,
              "Khi Roll dang cooldown, phai ha cap xuong Stutter-step di chuyen");
    }

    // ----------------------------------------------------------
    // 5. Sub-test 5: Đo đạc hiệu năng Hot Path 120Hz (< 0.10ms)
    // ----------------------------------------------------------
    {
        TelemetryPacket stressPacket{};
        stressPacket.player.posX = 200.0f;
        stressPacket.player.posY = 200.0f;
        stressPacket.player.maxHP = 1;
        stressPacket.player.currentHP = 1;
        stressPacket.player.maxES = 3500;
        stressPacket.player.currentES = 3500;
        stressPacket.entityCount = 64;

        for (uint32_t i = 0; i < 64; ++i) {
            auto& ent = stressPacket.entities[i];
            ent.id = 2000 + i;
            ent.type = 1;
            ent.currentHP = 1000;
            ent.maxHP = 1000;
            ent.posX = 200.0f + static_cast<float>((i % 8) - 4) * 20.0f;
            ent.posY = 200.0f + static_cast<float>((i / 8) - 4) * 20.0f;
            ent.extraFlags = 0;
            if (i == 3) {
                ent.rarity = 2; // Rare
                ent.currentAnimationId = 112; // Slam
            }
        }

        constexpr int kIters = 5000;
        const auto tStart = std::chrono::high_resolution_clock::now();

        for (int i = 0; i < kIters; ++i) {
            arbiter.EvaluateThreats(stressPacket, 60000 + i * 8);
        }

        const auto tEnd = std::chrono::high_resolution_clock::now();
        const double totalUs = std::chrono::duration<double, std::micro>(tEnd - tStart).count();
        const double avgUs = totalUs / static_cast<double>(kIters);

        std::cout << "  -> Telegraph Evasion 64 entities scan latency: "
                  << avgUs << " µs/call (Yêu cầu: < 100.0 µs / 0.10ms)" << std::endl;

        CHECK(avgUs < 100.0, "Do tre TelegraphEvasionArbiter phai < 0.10ms (100µs)");
    }

    std::cout << "  -> TelegraphEvasionArbiter (Circle, Cone, Rect, DOD 120Hz) OK" << std::endl;
}
