#include "ArenaSimulation.h"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <string>

#include <QVector2D>

#include "AiSystem.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "CombatSystem.h"
#include "DynamicBodySystem.h"
#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "HealthComponent.h"
#include "HqProxyComponent.h"
#include "ModuleOwnerComponent.h"
#include "MovementIntentSystem.h"
#include "PositionComponent.h"
#include "RepairSystem.h"
#include "SalvagerSystem.h"
#include "DebrisSystem.h"
#include "ShipIdentityComponent.h"
#include "ShipSystem.h"
#include "ShipsConfig.h"
#include "StationBodyComponent.h"
#include "StationsConfig.h"
#include "SurfaceMask.h"
#include "ThreatCostCalculator.h"
#include "WeaponComponent.h"

ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
                                 ArenaConfig arenaConfig,
                                 unsigned int seed)
    : m_gameConfig(gameConfig)
    , m_arenaConfig(std::move(arenaConfig))
    , m_rng(seed)
    , m_currentTick(0)
    , m_beltSystem(1.0)
    , m_team1HqEntity(entt::null)
    , m_team2HqEntity(entt::null)
    , m_finished(false)
    , m_stopRequested(false)
{
    m_factoryState = makeFactoryState(m_gameConfig);

    // No ProductionSystem here: the arena stages ships directly and never runs a
    // factory, so nothing ticks material flow (ProductionSystem.h).
    m_buildingSystem = std::make_unique<BuildingSystem>(m_gameConfig);

    m_shipSystem     = std::make_unique<ShipSystem>(m_gameConfig, m_admin);
    // Arena fights are symmetric and aggressive: player-faction ships must not
    // retreat (REQ-BAL-SIM-AI). Only one faction would otherwise get retreat.
    m_shipSystem->setRetreatEnabled(false);
    m_aiSystem       = std::make_unique<AiSystem>(m_gameConfig);
    m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
    m_dynamicBodySystem    = std::make_unique<DynamicBodySystem>();
    m_combatSystem   = std::make_unique<CombatSystem>(m_gameConfig);
    m_debrisSystem    = std::make_unique<DebrisSystem>(m_admin);
    m_salvagerSystem = std::make_unique<SalvagerSystem>(m_admin);
    m_repairSystem   = std::make_unique<RepairSystem>(m_admin);

    // Static accumulated threat per team: sum of count * per-ship threat cost
    // (REQ-MOD-THREAT) over the configured ship roster. Ships only; HQ and
    // defence stations are excluded. Level-independent, so computed once here.
    for (int ti = 0; ti < 2; ++ti)
    {
        double teamThreat = 0.0;
        for (const ArenaShipEntry& shipEntry : m_arenaConfig.teams[ti].ships)
        {
            const std::vector<PlacedModule>& modules = shipEntry.layout
                ? shipEntry.layout->placedModules
                : std::vector<PlacedModule>{};
            const double shipThreat = calculateShipThreatCost(
                m_gameConfig.threatCosts, m_gameConfig, shipEntry.schematicId, modules);
            teamThreat += shipThreat * shipEntry.count;
        }
        m_teamThreat[ti] = teamThreat;
    }

    placeStructures();
    spawnShips();
    computeTeamMaxEhp();

    m_shipSystem->triggerRallyDeparture();

    updateStatus();
}

std::string ArenaStatus::TeamStatus::getEhpPercentText() const
{
    if (maxEhp <= 0.0)
    {
        return "n/a";
    }
    const int percent = static_cast<int>(std::lround(100.0 * currentEhp / maxEhp));
    return std::to_string(percent) + "%";
}

void ArenaSimulation::computeTeamMaxEhp()
{
    m_teamMaxEhp[0] = 0.0;
    m_teamMaxEhp[1] = 0.0;

    // Ships contribute their full max HP.
    m_admin.forEach<ShipIdentityComponent, FactionComponent, HealthComponent>(
        [this](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
               const FactionComponent& f, const HealthComponent& h)
        {
            m_teamMaxEhp[f.isEnemy ? 1 : 0] += static_cast<double>(h.maxHp);
        });

    // Defence stations contribute their full max HP; the HQ is excluded.
    m_admin.forEach<StationBodyComponent, FactionComponent, HealthComponent>(
        [this](entt::entity e, const StationBodyComponent& /*sb*/,
               const FactionComponent& f, const HealthComponent& h)
        {
            if (m_admin.hasAll<HqProxyComponent>(e))
            {
                return;
            }
            m_teamMaxEhp[f.isEnemy ? 1 : 0] += static_cast<double>(h.maxHp);
        });
}

ArenaSimulation::~ArenaSimulation() = default;


void ArenaSimulation::placeStructures()
{
    const int totalWidth = m_arenaConfig.playerBufferWidth_tiles
                         + m_arenaConfig.contestZoneWidth_tiles
                         + m_arenaConfig.enemyBufferWidth_tiles;
    const int midY = m_arenaConfig.heightTiles / 2;

    // Team 1 HQ — ECS proxy entity, player faction (isEnemy=false).
    {
        const ParsedSurfaceMask hqParsed =
            parseSurfaceMask(m_gameConfig.stations.hq.surfaceMask, Rotation::East);
        const int anchorX = 0;
        const int anchorY = midY - hqParsed.footprint.height() / 2;
        const float hp = static_cast<float>(
            m_gameConfig.stations.hq.hpFormula.evaluate(1.0));
        const QPoint anchor(anchorX, anchorY);
        std::vector<QPoint> absCells;
        for (const QPoint& rel : hqParsed.bodyCells)
        {
            absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
        }
        m_team1HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
                                                hp, hp, false);
        // Tag as an HQ so it is excluded from repair targeting (REQ-SHP-REPAIR).
        m_admin.addComponent<HqProxyComponent>(m_team1HqEntity);
        m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
                                                allocateBuildingId(m_factoryState));
    }

    // Team 2 HQ — ECS proxy entity, enemy faction (isEnemy=true). No weapon.
    {
        const ParsedSurfaceMask hqParsed =
            parseSurfaceMask(m_gameConfig.stations.hq.surfaceMask, Rotation::West);
        const int anchorX = totalWidth - hqParsed.footprint.width();
        const int anchorY = midY - hqParsed.footprint.height() / 2;
        const float hp = static_cast<float>(
            m_gameConfig.stations.hq.hpFormula.evaluate(1.0));
        const QPoint anchor(anchorX, anchorY);
        std::vector<QPoint> absCells;
        for (const QPoint& rel : hqParsed.bodyCells)
        {
            absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
        }
        m_team2HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
                                                hp, hp, true);
        // Tag as an HQ so it is excluded from repair targeting (REQ-SHP-REPAIR).
        m_admin.addComponent<HqProxyComponent>(m_team2HqEntity);
        m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
                                                allocateBuildingId(m_factoryState));
    }

    auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy)
    {
        float hp = 0.0f;
        WeaponComponent weapon;
        weapon.cooldownTicks = 0.0f;
        weapon.currentTarget = std::nullopt;
        const double lv = static_cast<double>(entry.level);
        const float tileSize = static_cast<float>(m_gameConfig.world.tileSize_m);

        const std::vector<std::string>& mask = isEnemy
            ? m_gameConfig.stations.enemyStation.surfaceMask
            : m_gameConfig.stations.playerStation.surfaceMask;

        if (entry.stationType == "player_station")
        {
            hp = static_cast<float>(
                m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
            weapon.damage     = static_cast<float>(
                m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
            weapon.range_tiles = static_cast<float>(
                m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv)) / tileSize;
            weapon.fireRateHz = static_cast<float>(
                m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
        }
        else
        {
            hp = static_cast<float>(
                m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
            weapon.damage     = static_cast<float>(
                m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
            weapon.range_tiles = static_cast<float>(
                m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv)) / tileSize;
            weapon.fireRateHz = static_cast<float>(
                m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
        }

        const ParsedSurfaceMask parsed = parseSurfaceMask(mask, Rotation::East);
        const QPoint& anchor = entry.position;
        std::vector<QPoint> absCells;
        for (const QPoint& rel : parsed.bodyCells)
        {
            absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
        }
        const entt::entity stationEntity = m_admin.spawnStation(
            anchor, parsed.footprint, absCells, hp, hp, isEnemy);
        {
            entt::entity wChild = m_admin.createModuleEntity();
            m_admin.addComponent<WeaponComponent>(wChild, weapon);
            m_admin.addComponent<ModuleOwnerComponent>(wChild,
                ModuleOwnerComponent{stationEntity});
        }
        m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
                                                allocateBuildingId(m_factoryState));
    };

    for (const ArenaStationEntry& entry : m_arenaConfig.teams[0].stations)
    {
        placeArenaStation(entry, false);
    }
    for (const ArenaStationEntry& entry : m_arenaConfig.teams[1].stations)
    {
        placeArenaStation(entry, true);
    }
}

void ArenaSimulation::spawnShips()
{
    const int contestStart = m_arenaConfig.playerBufferWidth_tiles;
    const int team2Start = contestStart + m_arenaConfig.contestZoneWidth_tiles;
    const int totalWidth = team2Start + m_arenaConfig.enemyBufferWidth_tiles;

    std::uniform_real_distribution<float> yDist(0.0f,
        static_cast<float>(m_arenaConfig.heightTiles));

    // Team 1: isEnemy=false, spawn in player buffer zone.
    {
        std::uniform_real_distribution<float> xDist(0.0f,
            static_cast<float>(m_arenaConfig.playerBufferWidth_tiles));

        for (const ArenaShipEntry& entry : m_arenaConfig.teams[0].ships)
        {
            for (int i = 0; i < entry.count; ++i)
            {
                const QVector2D pos(xDist(m_rng), yDist(m_rng));
                m_shipSystem->spawn(entry.schematicId, pos, false,
                                    entry.layout);
            }
        }
    }

    // Team 2: isEnemy=true, spawn in enemy buffer zone.
    {
        std::uniform_real_distribution<float> xDist(
            static_cast<float>(team2Start),
            static_cast<float>(totalWidth));

        for (const ArenaShipEntry& entry : m_arenaConfig.teams[1].ships)
        {
            for (int i = 0; i < entry.count; ++i)
            {
                const QVector2D pos(xDist(m_rng), yDist(m_rng));
                m_shipSystem->spawn(entry.schematicId, pos, true,
                                    entry.layout);
            }
        }
    }
}

void ArenaSimulation::run()
{
    while (!m_finished && !m_stopRequested.load(std::memory_order_relaxed))
    {
        tick();
    }

    {
        std::lock_guard<std::mutex> lock(m_statusMutex);
        m_status.finished = true;
    }
}

void ArenaSimulation::requestStop()
{
    m_stopRequested.store(true, std::memory_order_relaxed);
}

ArenaStatus ArenaSimulation::getStatus() const
{
    std::lock_guard<std::mutex> lock(m_statusMutex);
    return m_status;
}

void ArenaSimulation::tick()
{
    // Ship behavior systems (tick step 7): evaluate, select winner, execute.
    // Module + combat systems emit their tool beams into a shared buffer.
    m_shipSystem->clearMovementIntents();
    m_aiSystem->tick(m_admin, m_factoryState);
    std::vector<BeamFiredEvent> beamFiredEvents;
    m_salvagerSystem->tick(m_currentTick, m_factoryState, beamFiredEvents);
    m_repairSystem->tick(m_currentTick, beamFiredEvents);

    // Combat resolution (tick step 8).
    m_combatSystem->tick(m_currentTick, m_admin, beamFiredEvents);
    m_beamFiredEvents.insert(m_beamFiredEvents.end(), beamFiredEvents.begin(), beamFiredEvents.end());
    m_combatSystem->applyPendingDamage(m_currentTick, m_admin);

    // Deaths (tick step 9, simplified).
    tickDeaths();

    // Movement (tick step 10).
    m_movementIntentSystem->tick(m_admin);
    m_dynamicBodySystem->tick(m_admin);

    // Scrap despawn (tick step 11).
    m_debrisSystem->tickDespawn(m_currentTick);

    ++m_currentTick;

    if (m_currentTick % 30 == 0)
    {
        updateStatus();
    }
}

void ArenaSimulation::tickDeaths()
{
    // Dead ships.
    std::vector<entt::entity> deadShips;
    m_admin.forEach<ShipIdentityComponent, HealthComponent>(
        [&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/,
                     const HealthComponent& h)
        {
            if (h.hp <= 0.0f)
            {
                deadShips.push_back(e);
            }
        });

    for (entt::entity deadEntity : deadShips)
    {
        const ShipIdentityComponent& si = m_admin.get<ShipIdentityComponent>(deadEntity);
        const PositionComponent& pos    = m_admin.get<PositionComponent>(deadEntity);
        if (si.scrapDrop > 0)
        {
            const Tick despawnAt = m_currentTick
                + secondsToTicks(m_gameConfig.world.debrisDespawnSeconds);
            m_debrisSystem->spawn(pos.value, si.scrapDrop, despawnAt);
        }
        m_shipSystem->despawn(deadEntity);
    }

    // Dead stations.
    std::vector<entt::entity> deadStations;
    m_admin.forEach<StationBodyComponent, HealthComponent>(
        [&deadStations](entt::entity e, const StationBodyComponent& /*sb*/,
                        const HealthComponent& h)
        {
            if (h.hp <= 0.0f)
            {
                deadStations.push_back(e);
            }
        });

    for (entt::entity deadEntity : deadStations)
    {
        const StationBodyComponent& sb = m_admin.get<StationBodyComponent>(deadEntity);
        m_buildingSystem->unregisterTileOccupancy(m_factoryState, sb.bodyCells);
        {
            std::vector<entt::entity> stationChildren;
            m_admin.forEach<ModuleOwnerComponent>(
                [&](entt::entity ce, const ModuleOwnerComponent& o)
                {
                    if (o.owner == deadEntity) { stationChildren.push_back(ce); }
                });
            for (entt::entity ce : stationChildren) { m_admin.destroy(ce); }
        }
        m_admin.destroy(deadEntity);
    }

    // Check end conditions — HQ proxy entities.
    const bool team1HqGone = !m_admin.isValid(m_team1HqEntity)
        || m_admin.get<HealthComponent>(m_team1HqEntity).hp <= 0.0f;
    const bool team2HqGone = !m_admin.isValid(m_team2HqEntity)
        || m_admin.get<HealthComponent>(m_team2HqEntity).hp <= 0.0f;

    if (team1HqGone || team2HqGone)
    {
        m_finished = true;
        m_winnerTeam = team1HqGone ? 1 : 0;
        updateStatus();
        return;
    }

    // Check if all ships and defence stations of one team are destroyed.
    bool team1HasUnits = false;
    bool team2HasUnits = false;
    m_admin.forEach<ShipIdentityComponent, FactionComponent>(
        [&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
                                         const ShipIdentityComponent& /*si*/,
                                         const FactionComponent& f)
        {
            if (f.isEnemy) { team2HasUnits = true; }
            else           { team1HasUnits = true; }
        });

    m_admin.forEach<StationBodyComponent, FactionComponent>(
        [this, &team1HasUnits, &team2HasUnits](entt::entity e,
                                          const StationBodyComponent& /*sb*/,
                                          const FactionComponent& f)
        {
            // The HQ carries a StationBodyComponent but is not a defence station;
            // its destruction is a separate end condition (REQ-BAL-SIM-END).
            if (m_admin.hasAll<HqProxyComponent>(e)) { return; }
            if (f.isEnemy) { team2HasUnits = true; }
            else           { team1HasUnits = true; }
        });

    if (!team1HasUnits || !team2HasUnits)
    {
        m_finished = true;
        m_winnerTeam = team1HasUnits ? 0 : 1;
        updateStatus();
    }
}

void ArenaSimulation::tickOnce()
{
    if (!m_finished)
    {
        tick();
        updateStatus();
    }
}

std::vector<BeamFiredEvent> ArenaSimulation::drainBeamFiredEvents()
{
    std::vector<BeamFiredEvent> result;
    result.swap(m_beamFiredEvents);
    return result;
}

bool ArenaSimulation::isFinished() const
{
    return m_finished;
}

std::optional<int> ArenaSimulation::getWinnerTeam() const
{
    return m_winnerTeam;
}

Tick ArenaSimulation::getCurrentTick() const
{
    return m_currentTick;
}

const ArenaConfig& ArenaSimulation::getArenaConfig() const
{
    return m_arenaConfig;
}

const FactoryState& ArenaSimulation::getFactoryState() const
{
    return m_factoryState;
}

const ShipSystem& ArenaSimulation::getShips() const
{
    return *m_shipSystem;
}

const DebrisSystem& ArenaSimulation::getDebrisSystem() const
{
    return *m_debrisSystem;
}

EntityAdmin& ArenaSimulation::getAdmin()
{
    return m_admin;
}

const EntityAdmin& ArenaSimulation::getAdmin() const
{
    return m_admin;
}

void ArenaSimulation::updateStatus()
{
    ArenaStatus newStatus;
    newStatus.finished = m_finished;
    newStatus.winnerTeam = m_winnerTeam;
    newStatus.durationSeconds = ticksToSeconds(m_currentTick);

    // Live remaining HP of each team's ships and defence stations (HQ excluded);
    // the EHP-percentage numerator (denominator is the fixed m_teamMaxEhp).
    double currentEhp[2] = {0.0, 0.0};
    m_admin.forEach<ShipIdentityComponent, FactionComponent, HealthComponent>(
        [&currentEhp](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
                      const FactionComponent& f, const HealthComponent& h)
        {
            if (h.hp > 0.0f)
            {
                currentEhp[f.isEnemy ? 1 : 0] += static_cast<double>(h.hp);
            }
        });
    m_admin.forEach<StationBodyComponent, FactionComponent, HealthComponent>(
        [this, &currentEhp](entt::entity e, const StationBodyComponent& /*sb*/,
                            const FactionComponent& f, const HealthComponent& h)
        {
            if (m_admin.hasAll<HqProxyComponent>(e))
            {
                return;
            }
            if (h.hp > 0.0f)
            {
                currentEhp[f.isEnemy ? 1 : 0] += static_cast<double>(h.hp);
            }
        });

    for (int ti = 0; ti < 2; ++ti)
    {
        ArenaStatus::TeamStatus& teamStatus = newStatus.teams[ti];
        teamStatus.name = m_arenaConfig.teams[ti].name;
        teamStatus.threatLevel = m_teamThreat[ti];
        teamStatus.currentEhp = currentEhp[ti];
        teamStatus.maxEhp = m_teamMaxEhp[ti];

        // HQ entry (always first).
        {
            ArenaStatus::Entry hqEntry;
            hqEntry.displayName = "HQ";
            hqEntry.level = 1;
            hqEntry.total = 1;
            const entt::entity hqEntity = (ti == 0) ? m_team1HqEntity : m_team2HqEntity;
            hqEntry.surviving = (m_admin.isValid(hqEntity)
                && m_admin.get<HealthComponent>(hqEntity).hp > 0.0f) ? 1 : 0;
            teamStatus.entries.push_back(hqEntry);
        }

        // Ship entries.
        for (const ArenaShipEntry& shipEntry : m_arenaConfig.teams[ti].ships)
        {
            ArenaStatus::Entry entry;
            entry.displayName = shipEntry.schematicId;
            // Ships no longer carry a level (level suffix stays empty).
            entry.total = shipEntry.count;

            int surviving = 0;
            const bool isEnemyTeam = (ti == 1);
            m_admin.forEach<ShipIdentityComponent, FactionComponent, HealthComponent>(
                [&surviving, &shipEntry, isEnemyTeam](entt::entity /*e*/,
                    const ShipIdentityComponent& si, const FactionComponent& f,
                    const HealthComponent& h)
                {
                    if (f.isEnemy == isEnemyTeam
                        && si.schematicId == shipEntry.schematicId
                        && h.hp > 0.0f)
                    {
                        ++surviving;
                    }
                });
            entry.surviving = surviving;
            teamStatus.entries.push_back(entry);
        }

        // Station entries.
        for (std::size_t si = 0; si < m_arenaConfig.teams[ti].stations.size(); ++si)
        {
            const ArenaStationEntry& stationEntry = m_arenaConfig.teams[ti].stations[si];

            ArenaStatus::Entry entry;
            entry.displayName = "Station";
            entry.level = stationEntry.level;
            entry.total = 1;

            int surviving = 0;
            const bool isEnemyTeam = (ti == 1);
            m_admin.forEach<StationBodyComponent, FactionComponent, HealthComponent>(
                [&surviving, &stationEntry, isEnemyTeam](entt::entity /*e*/,
                    const StationBodyComponent& sb, const FactionComponent& f,
                    const HealthComponent& h)
                {
                    if (f.isEnemy == isEnemyTeam
                        && sb.anchor == stationEntry.position
                        && h.hp > 0.0f)
                    {
                        surviving = 1;
                    }
                });
            entry.surviving = surviving;
            teamStatus.entries.push_back(entry);
        }
    }

    std::lock_guard<std::mutex> lock(m_statusMutex);
    m_status = newStatus;
}
