#include "ShipStatsCalculator.h"

#include <map>
#include <stdexcept>
#include <utility>

#include "CargoComponent.h"
#include "DynamicBodyComponent.h"
#include "EntityAdmin.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "RepairToolComponent.h"
#include "SalvagerComponent.h"
#include "SensorRangeComponent.h"
#include "Tick.h"
#include "WeaponComponent.h"

ShipStats calculateShipStats(const GameConfig& config,
                              const std::string& shipId,
                              const std::vector<PlacedModule>& modules)
{
    ShipStats result{};

    const ShipDef* shipDef = config.ships.findShipDef(shipId);
    if (!shipDef) { return result; }

    const double tileSize = config.world.tileSize_m;

    // --- Base hull stats (convert from SI to display units) ------------------
    result.hp = shipDef->health.hp;
    result.maxSpeed_tps = static_cast<float>(
        shipDef->movement.speed_mps / tileSize);
    result.sensorRange_tiles = static_cast<float>(
        shipDef->sensor.sensorRange_m / tileSize);
    result.mainAcceleration_tpss = static_cast<float>(
        shipDef->movement.mainAcceleration_mpss / tileSize);
    result.maneuveringAcceleration_tpss = static_cast<float>(
        shipDef->movement.maneuveringAcceleration_mpss / tileSize);
    result.angularAcceleration_radpss = shipDef->movement.angularAcceleration_radpss;
    result.maxRotationSpeed_radps = shipDef->movement.maxRotationSpeed_radps;

    // --- Pass 1: base capability stats per module instance -------------------
    struct WeaponInstance  { float damage; float range_tiles; float rate_hz; };
    struct SalvageInstance { float range_tiles; float rate; };
    struct RepairInstance  { float rate_hz; float amount_hp; float range_tiles; };

    std::vector<WeaponInstance>  weaponInstances;
    std::vector<SalvageInstance> salvageInstances;
    std::vector<RepairInstance>  repairInstances;

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

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

        if (def->weaponCapability)
        {
            WeaponInstance wi;
            wi.damage      = def->weaponCapability->damage;
            wi.range_tiles = static_cast<float>(def->weaponCapability->attackRange_m / tileSize);
            wi.rate_hz     = def->weaponCapability->attackRate_hz;
            weaponInstances.push_back(wi);
        }
        if (def->salvageCapability)
        {
            cargoCapacityBase += def->salvageCapability->cargoCapacity;

            SalvageInstance si;
            si.range_tiles = static_cast<float>(def->salvageCapability->collectionRange_m / tileSize);
            si.rate        = def->salvageCapability->collectionRate_hz;
            salvageInstances.push_back(si);
        }
        if (def->repairCapability)
        {
            RepairInstance ri;
            ri.rate_hz     = def->repairCapability->repairRate_hz;
            ri.amount_hp   = def->repairCapability->repairAmountHp;
            ri.range_tiles = static_cast<float>(def->repairCapability->repairRange_m / tileSize);
            repairInstances.push_back(ri);
        }
    }

    // --- Pass 2: accumulate passive stat modifiers ---------------------------
    // Mirrors ShipSystem::spawn() routing logic exactly.
    std::map<std::string, std::pair<double, double>> hullMods;
    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;
    std::map<std::string, std::pair<double, double>> cargoMods;

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

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

            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 additive modifiers are in metres in config; convert to tiles.
    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 /= tileSize;
            }
        }
    }

    // Acceleration additive modifiers are in m/s² in config; convert to tiles/s².
    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 /= tileSize;
            }
        }
    }

    auto applyMod = [](float& stat, const std::string& name,
                       const std::map<std::string, std::pair<double, double>>& mods)
    {
        const std::map<std::string, std::pair<double, double>>::const_iterator 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.
    applyMod(result.hp,                              "hp",                       hullMods);
    applyMod(result.maxSpeed_tps,                    "speed",                    hullMods);
    applyMod(result.mainAcceleration_tpss,           "main_acceleration",        hullMods);
    applyMod(result.maneuveringAcceleration_tpss,    "maneuvering_acceleration", hullMods);
    applyMod(result.angularAcceleration_radpss,      "angular_acceleration",     hullMods);
    applyMod(result.maxRotationSpeed_radps,          "max_rotation_speed",       hullMods);
    applyMod(result.sensorRange_tiles,               "sensor_range",             hullMods);

    // Apply weapon modifiers and compute combined stats.
    if (!weaponInstances.empty())
    {
        float combinedDps = 0.0f;
        float maxRange    = 0.0f;
        for (WeaponInstance& wi : weaponInstances)
        {
            applyMod(wi.damage,      "damage",       weaponMods);
            applyMod(wi.range_tiles, "attack_range", weaponMods);
            applyMod(wi.rate_hz,     "attack_rate",  weaponMods);
            combinedDps += wi.damage * wi.rate_hz;
            if (wi.range_tiles > maxRange) { maxRange = wi.range_tiles; }
        }
        result.weapons = ShipStats::WeaponStats{combinedDps, maxRange};
    }

    // Apply salvage modifiers and compute combined stats.
    if (!salvageInstances.empty())
    {
        float combinedRate = 0.0f;
        float maxRange     = 0.0f;
        for (SalvageInstance& si : salvageInstances)
        {
            applyMod(si.range_tiles, "collection_range", salvageMods);
            applyMod(si.rate,        "collection_rate",  salvageMods);
            combinedRate += si.rate;
            if (si.range_tiles > maxRange) { maxRange = si.range_tiles; }
        }
        result.salvage = ShipStats::SalvageStats{combinedRate, maxRange};
    }

    // Cargo capacity is a ship-level stat: apply [module.cargo] modifiers to the
    // summed base (REQ-MOD-CARGO-CAPACITY).
    {
        float capacity = static_cast<float>(cargoCapacityBase);
        applyMod(capacity, "cargo_capacity", cargoMods);
        result.cargoCapacity = static_cast<int>(capacity + 0.5f);
    }

    // Apply repair modifiers and compute combined stats.
    if (!repairInstances.empty())
    {
        float combinedRate = 0.0f;
        float maxRange     = 0.0f;
        for (RepairInstance& ri : repairInstances)
        {
            applyMod(ri.rate_hz,     "repair_rate",  repairMods);
            applyMod(ri.range_tiles, "repair_range", repairMods);
            combinedRate += ri.rate_hz * ri.amount_hp;
            if (ri.range_tiles > maxRange) { maxRange = ri.range_tiles; }
        }
        result.repair = ShipStats::RepairStats{combinedRate, maxRange};
    }

    return result;
}

ShipStats buildShipStatsFromEntity(const EntityAdmin& admin, entt::entity shipEntity)
{
    ShipStats result{};

    const HealthComponent& health = admin.get<HealthComponent>(shipEntity);
    const DynamicBodyComponent& body = admin.get<DynamicBodyComponent>(shipEntity);
    const SensorRangeComponent& sensor = admin.get<SensorRangeComponent>(shipEntity);

    result.hp = health.maxHp;
    result.maxSpeed_tps = body.maxSpeed_tpt * kTickRateHz;
    result.sensorRange_tiles = sensor.value_tiles;
    result.mainAcceleration_tpss = body.mainAcceleration_tptt * kTickRateHz * kTickRateHz;
    result.maneuveringAcceleration_tpss = body.maneuveringAcceleration_tptt * kTickRateHz * kTickRateHz;
    result.angularAcceleration_radpss = body.maxAngularAcceleration_rptt * kTickRateHz * kTickRateHz;
    result.maxRotationSpeed_radps = body.maxRotationSpeed_rpt * kTickRateHz;

    float weaponDps = 0.0f;
    float weaponMaxRange = 0.0f;
    bool hasWeapons = false;

    float salvageRate = 0.0f;
    float salvageMaxRange = 0.0f;
    bool hasSalvage = false;

    float repairRate = 0.0f;
    float repairMaxRange = 0.0f;
    bool hasRepair = false;

    admin.forEach<ModuleOwnerComponent, WeaponComponent>(
        [&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const WeaponComponent& w)
        {
            if (owner.owner != shipEntity) { return; }
            hasWeapons = true;
            weaponDps += w.damage * w.fireRateHz;
            if (w.range_tiles > weaponMaxRange) { weaponMaxRange = w.range_tiles; }
        });

    admin.forEach<ModuleOwnerComponent, SalvagerComponent>(
        [&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const SalvagerComponent& s)
        {
            if (owner.owner != shipEntity) { return; }
            hasSalvage = true;
            const float rate = (s.collectionIntervalTicks > 0)
                ? static_cast<float>(kTickRateHz) / static_cast<float>(s.collectionIntervalTicks)
                : 0.0f;
            salvageRate += rate;
            if (s.collectionRange_tiles > salvageMaxRange) { salvageMaxRange = s.collectionRange_tiles; }
        });

    admin.forEach<CargoComponent>(
        [&](entt::entity ship, const CargoComponent& c)
        {
            if (ship == shipEntity) { result.cargoCapacity = c.maxCapacity; }
        });

    admin.forEach<ModuleOwnerComponent, RepairToolComponent>(
        [&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const RepairToolComponent& r)
        {
            if (owner.owner != shipEntity) { return; }
            hasRepair = true;
            const float cyclesPerSec = (r.repairIntervalTicks > 0)
                ? static_cast<float>(kTickRateHz) / static_cast<float>(r.repairIntervalTicks)
                : 0.0f;
            repairRate += cyclesPerSec * r.repairAmountHp;
            if (r.range_tiles > repairMaxRange) { repairMaxRange = r.range_tiles; }
        });

    if (hasWeapons) { result.weapons = ShipStats::WeaponStats{weaponDps, weaponMaxRange}; }
    if (hasSalvage) { result.salvage = ShipStats::SalvageStats{salvageRate, salvageMaxRange}; }
    if (hasRepair)  { result.repair  = ShipStats::RepairStats{repairRate, repairMaxRange}; }

    return result;
}
