#include "ShipSystem.h"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <map>
#include <stdexcept>
#include <utility>
#include <vector>

#include "AdvanceBehavior.h"
#include "AttackBehavior.h"
#include "BehaviorScores.h"
#include "CargoComponent.h"
#include "DeliverScrapBehavior.h"
#include "DynamicBodyComponent.h"
#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "ModulesConfig.h"
#include "MovementIntentComponent.h"
#include "RallyBehavior.h"
#include "RepairBehavior.h"
#include "RepairToolComponent.h"
#include "RetreatBehavior.h"
#include "SalvageScrapBehavior.h"
#include "SalvagerComponent.h"
#include "SelectedBehaviorComponent.h"
#include "SensorRangeComponent.h"
#include "ShipIdentityComponent.h"
#include "StandbyBehavior.h"
#include "ThreatCostCalculator.h"
#include "Tick.h"
#include "tracing.h"
#include "WeaponComponent.h"

ShipSystem::ShipSystem(const GameConfig& config, EntityAdmin& admin)
    : m_config(config)
    , m_admin(admin)
{
}

entt::entity ShipSystem::spawn(const std::string& schematicId,
                               QVector2D position, bool isEnemy,
                               const std::optional<ShipLayoutConfig>& layout)
{
    const ShipDef* def = m_config.ships.findShipDef(schematicId);
    assert(def != nullptr);

    const float tickRate = static_cast<float>(kTickRateHz);
    const float tileSize = static_cast<float>(m_config.world.tileSize_m);

    float hp    = def->health.hp;
    float maxHp = hp;
    float maxSpeed_tpt              = def->movement.speed_mps / tileSize / tickRate;
    float mainAcceleration_tptt     = def->movement.mainAcceleration_mpss / tileSize / tickRate;
    float maneuveringAcceleration_tptt = def->movement.maneuveringAcceleration_mpss
                                          / tileSize / tickRate;
    float maxAngularAcceleration_rptt  = def->movement.angularAcceleration_radpss / tickRate;
    float maxRotationSpeed_rpt      = def->movement.maxRotationSpeed_radps / tickRate;
    float sensorRange_tiles         = def->sensor.sensorRange_m / tileSize;

    entt::entity entity = m_admin.spawnShip(
        position, hp, maxHp,
        maxSpeed_tpt, mainAcceleration_tptt, maneuveringAcceleration_tptt,
        maxAngularAcceleration_rptt, maxRotationSpeed_rpt, sensorRange_tiles,
        schematicId, isEnemy);

    // Determine module list: configured layout takes precedence over default.
    const std::vector<PlacedModule>& modules =
        layout.has_value() ? layout->placedModules : def->defaultModules;

    // Derive the scrap dropped on destruction from the ship's as-built threat cost
    // (REQ-RES-DEBRIS-DROP): round(threat * scrap_per_threat), floored at 1 for any
    // ship with threat > 0. Computed once here since threat is level-independent.
    const double threatCost = calculateShipThreatCost(m_config.threatCosts, m_config,
                                                      schematicId, modules);
    const int scrapDrop = threatCost > 0.0
        ? std::max(1, static_cast<int>(std::lround(threatCost * m_config.world.scrapPerThreat)))
        : 0;
    m_admin.get<ShipIdentityComponent>(entity).scrapDrop = scrapDrop;

    // --- Pass 1: create capability child entities ----------------------------
    std::vector<entt::entity> weaponChildren;
    std::vector<entt::entity> salvageChildren;
    std::vector<entt::entity> repairChildren;

    // Cargo capacity is a ship-level stat (REQ-MOD-CARGO-CAPACITY): its base is the
    // sum of every cargo-providing module's contribution, accumulated here.
    double cargoCapacityBase = 0.0;

    for (const PlacedModule& pm : modules)
    {
        const ModuleDef* modDef = m_config.modules.findModuleDef(pm.moduleId);
        if (!modDef) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }

        if (modDef->weaponCapability)
        {
            WeaponComponent w;
            w.damage        = modDef->weaponCapability->damage;
            w.range_tiles   = modDef->weaponCapability->attackRange_m / tileSize;
            w.fireRateHz    = modDef->weaponCapability->attackRate_hz;
            w.cooldownTicks = 0.0f;
            w.currentTarget = std::nullopt;

            entt::entity child = m_admin.createModuleEntity();
            m_admin.addComponent<WeaponComponent>(child, w);
            m_admin.addComponent<ModuleOwnerComponent>(child, ModuleOwnerComponent{entity});
            weaponChildren.push_back(child);
        }

        if (modDef->salvageCapability)
        {
            cargoCapacityBase += modDef->salvageCapability->cargoCapacity;

            SalvagerComponent salvager;
            salvager.collectionRange_tiles = modDef->salvageCapability->collectionRange_m / tileSize;
            const double rate = modDef->salvageCapability->collectionRate_hz;
            salvager.collectionIntervalTicks = (rate > 0.0)
                ? static_cast<int>(kTickRateHz / rate + 0.5)
                : 0;
            salvager.cooldownTicksRemaining = 0;

            entt::entity child = m_admin.createModuleEntity();
            m_admin.addComponent<SalvagerComponent>(child, salvager);
            m_admin.addComponent<ModuleOwnerComponent>(child, ModuleOwnerComponent{entity});
            salvageChildren.push_back(child);
        }

        if (modDef->repairCapability)
        {
            RepairToolComponent rt;
            const double repairRateHz = modDef->repairCapability->repairRate_hz;
            rt.repairIntervalTicks = (repairRateHz > 0.0)
                ? static_cast<int>(kTickRateHz / repairRateHz + 0.5)
                : 0;
            rt.repairAmountHp = modDef->repairCapability->repairAmountHp;
            rt.cooldownTicksRemaining = 0;
            rt.range_tiles   = modDef->repairCapability->repairRange_m / tileSize;
            rt.currentTarget = std::nullopt;

            entt::entity child = m_admin.createModuleEntity();
            m_admin.addComponent<RepairToolComponent>(child, rt);
            m_admin.addComponent<ModuleOwnerComponent>(child, ModuleOwnerComponent{entity});
            repairChildren.push_back(child);
        }
    }

    // --- Pass 2: apply passive stat modifiers --------------------------------

    // Accumulate hull-level modifiers.
    std::map<std::string, std::pair<double, double>> hullMods;
    // Per-capability-type modifier accumulators (applied to each child).
    std::map<std::string, std::pair<double, double>> weaponMods;
    std::map<std::string, std::pair<double, double>> salvageMods;
    std::map<std::string, std::pair<double, double>> repairMods;
    // Ship-level cargo capacity modifiers ([module.cargo]); applied to the pool.
    std::map<std::string, std::pair<double, double>> cargoMods;

    for (const PlacedModule& pm : modules)
    {
        const ModuleDef* modDef = m_config.modules.findModuleDef(pm.moduleId);
        if (!modDef) { throw std::runtime_error("unknown module id '" + pm.moduleId + "'"); }

        for (const ModuleStatModifier& sm : modDef->statModifiers)
        {
            const double val = sm.value;

            // Route modifier to the correct accumulator by stat category.
            // weapon/salvage/repair stats go to the corresponding child map;
            // hull stats (hp, speed, sensor_range, …) go to hullMods.
            const bool isWeaponStat  = (sm.stat == "damage"
                                     || sm.stat == "attack_range"
                                     || sm.stat == "attack_rate");
            const bool isSalvageStat = (sm.stat == "collection_range");
            const bool isRepairStat  = (sm.stat == "repair_rate"
                                     || sm.stat == "repair_range");
            const bool isCargoStat   = (sm.stat == "cargo_capacity");

            std::map<std::string, std::pair<double, double>>* target = &hullMods;
            if (isWeaponStat)  { target = &weaponMods;  }
            if (isSalvageStat) { target = &salvageMods; }
            if (isRepairStat)  { target = &repairMods;  }
            if (isCargoStat)   { target = &cargoMods;   }

            std::pair<double, double>& acc = (*target)[sm.stat];
            if (sm.modifierType == "multiplicative")
            {
                acc.first += (val - 1.0);
            }
            else
            {
                acc.second += val;
            }
        }
    }

    // Range stat additive modifiers are expressed in metres in config; convert to tiles.
    const double tileSizeD  = static_cast<double>(m_config.world.tileSize_m);
    const double tickRateD  = static_cast<double>(kTickRateHz);
    const char* const kRangeStats[] = {
        "sensor_range", "attack_range", "collection_range", "repair_range"
    };
    std::map<std::string, std::pair<double, double>>* allModMaps[] = {
        &hullMods, &weaponMods, &salvageMods, &repairMods
    };
    for (const char* stat : kRangeStats)
    {
        for (std::map<std::string, std::pair<double, double>>* mods : allModMaps)
        {
            std::map<std::string, std::pair<double, double>>::iterator it = mods->find(stat);
            if (it != mods->end())
            {
                it->second.second /= tileSizeD;
            }
        }
    }

    // Acceleration additive modifiers are in m/s² in config; convert to tiles/tick
    // (same as the base spawn conversion: / tileSize / tickRate).
    const char* const kAccelerationStats[] = {
        "main_acceleration", "maneuvering_acceleration"
    };
    for (const char* stat : kAccelerationStats)
    {
        for (std::map<std::string, std::pair<double, double>>* mods : allModMaps)
        {
            std::map<std::string, std::pair<double, double>>::iterator it = mods->find(stat);
            if (it != mods->end())
            {
                it->second.second /= tileSizeD * tickRateD;
            }
        }
    }

    // Helper: apply a modifier map to a float stat.
    auto applyMod = [](float& stat, const std::string& name,
                       const std::map<std::string, std::pair<double, double>>& mods)
    {
        const auto it = mods.find(name);
        if (it != mods.end())
        {
            stat = static_cast<float>(
                static_cast<double>(stat) * (1.0 + it->second.first)
                + it->second.second);
        }
    };

    // Apply hull modifiers.
    {
        HealthComponent& health        = m_admin.get<HealthComponent>(entity);
        DynamicBodyComponent& dynamics = m_admin.get<DynamicBodyComponent>(entity);
        SensorRangeComponent& sensor   = m_admin.get<SensorRangeComponent>(entity);

        applyMod(health.maxHp,                                 "hp",                       hullMods);
        health.hp = health.maxHp;
        applyMod(dynamics.maxSpeed_tpt,                        "speed",                    hullMods);
        applyMod(dynamics.mainAcceleration_tptt,               "main_acceleration",        hullMods);
        applyMod(dynamics.maneuveringAcceleration_tptt,        "maneuvering_acceleration", hullMods);
        applyMod(dynamics.maxAngularAcceleration_rptt,         "angular_acceleration",     hullMods);
        applyMod(dynamics.maxRotationSpeed_rpt,                "max_rotation_speed",       hullMods);
        applyMod(sensor.value_tiles,                           "sensor_range",             hullMods);
    }

    // Apply weapon modifiers to each weapon child.
    for (entt::entity child : weaponChildren)
    {
        WeaponComponent& w = m_admin.get<WeaponComponent>(child);
        applyMod(w.damage,       "damage",       weaponMods);
        applyMod(w.range_tiles,  "attack_range", weaponMods);
        applyMod(w.fireRateHz,   "attack_rate",  weaponMods);
    }

    // Apply salvage modifiers to each salvage child.
    for (entt::entity child : salvageChildren)
    {
        SalvagerComponent& c = m_admin.get<SalvagerComponent>(child);
        float fRange = c.collectionRange_tiles;
        // Apply rate modifier: compute rate from interval, apply multiplier, convert back.
        float fRate = (c.collectionIntervalTicks > 0)
            ? static_cast<float>(kTickRateHz) / static_cast<float>(c.collectionIntervalTicks)
            : 0.0f;
        applyMod(fRange, "collection_range", salvageMods);
        applyMod(fRate,  "collection_rate",  salvageMods);
        c.collectionRange_tiles   = fRange;
        c.collectionIntervalTicks = (fRate > 0.0f)
            ? static_cast<int>(static_cast<float>(kTickRateHz) / fRate + 0.5f)
            : 0;
    }

    // Cargo capacity is a ship-level stat: apply [module.cargo] modifiers to the
    // summed base, then attach the shared cargo pool when the ship can hold anything
    // (REQ-MOD-CARGO-CAPACITY).
    {
        float fCapacity = static_cast<float>(cargoCapacityBase);
        applyMod(fCapacity, "cargo_capacity", cargoMods);
        const int maxCapacity = static_cast<int>(fCapacity + 0.5f);
        if (maxCapacity > 0)
        {
            m_admin.addComponent<CargoComponent>(entity, CargoComponent{maxCapacity, 0});
        }
    }

    // Apply repair modifiers to each repair child.
    for (entt::entity child : repairChildren)
    {
        RepairToolComponent& rt = m_admin.get<RepairToolComponent>(child);
        // Apply rate modifier: compute cycles/s from interval, apply, convert back.
        float fRate = (rt.repairIntervalTicks > 0)
            ? static_cast<float>(kTickRateHz) / static_cast<float>(rt.repairIntervalTicks)
            : 0.0f;
        applyMod(fRate,          "repair_rate",  repairMods);
        applyMod(rt.range_tiles, "repair_range", repairMods);
        rt.repairIntervalTicks = (fRate > 0.0f)
            ? static_cast<int>(static_cast<float>(kTickRateHz) / fRate + 0.5f)
            : 0;
    }

    // --- Pass 3: attach behavior components based on capability presence -----

    // Baseline: every ship can always fall back to advancing, and needs a slot
    // for the per-tick behavior selection result.
    m_admin.addComponent<AdvanceBehavior>(entity, AdvanceBehavior{});
    m_admin.addComponent<SelectedBehaviorComponent>(entity, SelectedBehaviorComponent{});

    // Player ships retreat to the rally point when threatened or badly damaged
    // (disabled by the balancing tool to keep arena fights symmetric).
    if (!isEnemy && m_retreatEnabled)
    {
        RetreatBehavior retreat;
        retreat.retreatHpFraction = BehaviorScores::kLowHpFraction;
        retreat.retreatPoint      = m_rallyPoint;
        m_admin.addComponent<RetreatBehavior>(entity, retreat);
    }

    if (!weaponChildren.empty())
    {
        float maxWeaponRange = 0.0f;
        for (entt::entity child : weaponChildren)
        {
            const float r = m_admin.get<WeaponComponent>(child).range_tiles;
            if (r > maxWeaponRange) { maxWeaponRange = r; }
        }

        AttackBehavior attack;
        attack.orbitRadius_tiles =
            maxWeaponRange * static_cast<float>(m_config.world.orbitFactor);
        m_admin.addComponent<AttackBehavior>(entity, attack);

        if (!isEnemy)
        {
            RallyBehavior rally;
            rally.rallyPoint       = m_rallyPoint;
            rally.orbitRadius_tiles =
                static_cast<float>(m_config.world.rallyOrbitRadius_tiles);
            m_admin.addComponent<RallyBehavior>(entity, rally);
        }
    }

    if (!salvageChildren.empty())
    {
        float maxCollRange = 0.0f;
        for (entt::entity child : salvageChildren)
        {
            const float r = m_admin.get<SalvagerComponent>(child).collectionRange_tiles;
            if (r > maxCollRange) { maxCollRange = r; }
        }

        SalvageScrapBehavior salvage;
        salvage.debrisTarget              = std::nullopt;
        salvage.maxCollectionRange_tiles = maxCollRange;
        salvage.orbitRadius_tiles =
            maxCollRange * static_cast<float>(m_config.world.orbitFactor);
        m_admin.addComponent<SalvageScrapBehavior>(entity, salvage);

        DeliverScrapBehavior deliver;  // deliveryBay starts unassigned (nullopt)
        m_admin.addComponent<DeliverScrapBehavior>(entity, deliver);
    }

    if (!repairChildren.empty())
    {
        float maxRepairRange = 0.0f;
        for (entt::entity child : repairChildren)
        {
            const float r = m_admin.get<RepairToolComponent>(child).range_tiles;
            if (r > maxRepairRange) { maxRepairRange = r; }
        }

        RepairBehavior repair;
        repair.currentTarget        = std::nullopt;
        repair.maxRepairRange_tiles = maxRepairRange;
        repair.orbitRadius_tiles =
            maxRepairRange * static_cast<float>(m_config.world.orbitFactor);
        m_admin.addComponent<RepairBehavior>(entity, repair);

        // Repair-capable ships hold with the fleet (REQ-SHP-STANDBY) instead of
        // charging the enemy when no more urgent behavior applies; this applies
        // whether or not the ship also carries weapons.
        m_admin.addComponent<StandbyBehavior>(entity, StandbyBehavior{});
    }

    return entity;
}

void ShipSystem::despawn(entt::entity entity)
{
    std::vector<entt::entity> children;
    m_admin.forEach<ModuleOwnerComponent>(
        [&](entt::entity e, const ModuleOwnerComponent& o)
        {
            if (o.owner == entity) { children.push_back(e); }
        });
    for (entt::entity child : children) { m_admin.destroy(child); }
    m_admin.destroy(entity);
}

void ShipSystem::clearMovementIntents()
{
	TRACE();
    m_admin.forEach<MovementIntentComponent>(
        [](entt::entity /*e*/, MovementIntentComponent& i)
        {
            i = MovementIntentComponent{false, QVector2D(0.0f, 0.0f)};
        });
}

void ShipSystem::setRallyPoint(QVector2D point)
{
    m_rallyPoint = point;
}

void ShipSystem::setRetreatEnabled(bool enabled)
{
    m_retreatEnabled = enabled;
}

void ShipSystem::triggerRallyDeparture()
{
	TRACE();
    std::vector<entt::entity> toRemove;
    m_admin.forEach<RallyBehavior, FactionComponent>(
        [&toRemove](entt::entity e, const RallyBehavior& /*rb*/,
                    const FactionComponent& f)
        {
            if (!f.isEnemy)
            {
                toRemove.push_back(e);
            }
        });
    for (entt::entity e : toRemove)
    {
        m_admin.removeComponent<RallyBehavior>(e);
    }
}
