"""
Headless Bot Swarm & Load Simulation Harness for FreeExile World Engine.
Simulates hundreds to thousands of concurrent headless player clients moving across the spatial grid,
submitting movement inputs, and verifying server tick processing latency and throughput.
"""

import time
import math
import random
import sys
import os

# Add server directory to path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))

from world.spatial_grid import SpatialGrid, Entity
from world.movement_authority import MovementAuthorityEngine, PlayerCharacter


class HeadlessBotSwarm:
    def __init__(self, bot_count: int = 1000, world_size: float = 1024.0):
        self.bot_count = bot_count
        self.world_size = world_size
        self.grid = SpatialGrid(cell_size=64.0)
        self.movement_engine = MovementAuthorityEngine(max_base_speed=6.0)
        self.bots: list = []

    def bootstrap(self):
        """Initializes simulated bot players across the 2.5D world map."""
        for i in range(self.bot_count):
            spawn_x = random.uniform(50.0, self.world_size - 50.0)
            spawn_y = random.uniform(50.0, self.world_size - 50.0)
            
            player = PlayerCharacter(
                entity_id=i + 1,
                x=spawn_x,
                y=spawn_y,
                move_speed=random.uniform(5.5, 6.5)
            )
            self.movement_engine.register_player(player)
            entity = Entity(entity_id=player.entity_id, x=spawn_x, y=spawn_y)
            self.grid.add_entity(entity)
            self.bots.append(player)

    def run_simulation(self, total_ticks: int = 30, dt: float = 0.0333) -> dict:
        """
        Runs the simulation loop for total_ticks (at 30Hz, 30 ticks = 1 second of in-game time).
        Measures execution time per tick, input throughput, and AOI query speeds.
        """
        tick_durations = []
        total_inputs_processed = 0

        for tick in range(total_ticks):
            start_time = time.perf_counter()

            # Process movement inputs for each bot in the swarm
            for player in self.bots:
                # Random walk direction
                angle = random.uniform(0, 2 * math.pi)
                dir_x = math.cos(angle)
                dir_y = math.sin(angle)

                success, new_x, new_y = self.movement_engine.process_move_input(
                    entity_id=player.entity_id,
                    dir_x=dir_x,
                    dir_y=dir_y,
                    dt=dt
                )
                if success:
                    # Update in spatial grid
                    entity = self.grid.entities.get(player.entity_id)
                    if entity:
                        self.grid.update_entity_position(entity, new_x, new_y)
                    total_inputs_processed += 1

            # Sample AOI proximity queries for 50 random observers
            sample_observers = random.sample(self.bots, min(50, len(self.bots)))
            for obs in sample_observers:
                _ = self.grid.get_entities_in_aoi(obs.x, obs.y, radius_cells=1)

            elapsed_ms = (time.perf_counter() - start_time) * 1000.0
            tick_durations.append(elapsed_ms)

        avg_tick_ms = sum(tick_durations) / len(tick_durations)
        max_tick_ms = max(tick_durations)
        min_tick_ms = min(tick_durations)
        throughput_inputs_sec = total_inputs_processed / (total_ticks * dt)

        return {
            "bot_count": self.bot_count,
            "total_ticks": total_ticks,
            "total_inputs": total_inputs_processed,
            "avg_tick_ms": avg_tick_ms,
            "max_tick_ms": max_tick_ms,
            "min_tick_ms": min_tick_ms,
            "throughput_inputs_sec": throughput_inputs_sec,
            "tick_budget_met": max_tick_ms < 33.33 # Must be well below 33.3ms budget for 30Hz
        }


def main():
    print("=" * 65)
    print("    FREEEXILE HEADLESS BOT SWARM LOAD SIMULATION HARNESS")
    print("=" * 65)

    swarm = HeadlessBotSwarm(bot_count=1000)
    print(f"\n[1] Bootstrapping {swarm.bot_count} simulated concurrent bot clients...")
    swarm.bootstrap()
    print("    Done. All entities indexed into Spatial Grid.")

    print("\n[2] Executing 30 ticks of authoritative movement & AOI queries (30Hz)...")
    results = swarm.run_simulation(total_ticks=30, dt=0.0333)

    print("\n[BENCHMARK RESULTS]")
    print(f"  * Concurrent Bots Simulated : {results['bot_count']}")
    print(f"  * Total Inputs Processed    : {results['total_inputs']}")
    print(f"  * Average Tick Duration     : {results['avg_tick_ms']:.3f} ms")
    print(f"  * Peak Tick Duration        : {results['max_tick_ms']:.3f} ms")
    print(f"  * Minimum Tick Duration     : {results['min_tick_ms']:.3f} ms")
    print(f"  * Input Throughput          : {results['throughput_inputs_sec']:,.0f} inputs/sec")
    print(f"  * 30Hz Budget (33.3ms) Met  : {'PASSED (EXCELLENT)' if results['tick_budget_met'] else 'FAILED'}")
    print("=" * 65)


if __name__ == "__main__":
    main()
