package com.livingworld.modules.ecosystem;

import com.livingworld.data.serialization.PersistenceReader;
import com.livingworld.data.serialization.PersistenceWriter;
import com.livingworld.events.LivingWorldEvent;
import com.livingworld.modules.ModuleContext;
import com.livingworld.modules.ModuleMetadata;
import com.livingworld.modules.ModuleUpdateResult;
import com.livingworld.modules.RegionUpdateContext;
import com.livingworld.modules.ServerContext;
import com.livingworld.modules.SimulationModule;
import com.livingworld.regions.Region;
import com.livingworld.regions.RegionMetrics;
import java.util.List;

/**
 * Integrates all ecosystem signals into a composite health score and manages
 * long-term stress and resilience dynamics.
 *
 * <p>This module runs <em>last</em> in the ecosystem update order, after
 * {@link com.livingworld.modules.pollution.PollutionModule},
 * {@link com.livingworld.modules.soil.SoilModule}, and
 * {@link com.livingworld.modules.vegetation.VegetationModule}, so it reads
 * fully updated current-tick values for all metrics.
 *
 * <h3>Ecosystem health formula</h3>
 * <pre>
 *   health = soilQuality      * 0.30
 *           + waterQuality     * 0.20
 *           + (100 - pollutionScore) * 0.30
 *           + vegetationPressure    * 0.20
 * </pre>
 *
 * <h3>Stress model</h3>
 * Danger zones are defined for each metric. Each metric in its danger zone
 * contributes 2.0 stress per tick. Stress decays by 0.5 per tick when no
 * dangers are active.
 *
 * <h3>Resilience and recovery</h3>
 * Resilience is a slow-moving trend indicator. It increases when the ecosystem
 * is healthy and decreases under prolonged stress. Recovery rate is derived
 * from resilience and current stress level.
 */
public final class EcosystemModule implements SimulationModule {

    public static final String MODULE_ID = "ecosystem";

    // Weights for the composite health score (must sum to 1.0).
    private static final double WEIGHT_SOIL       = 0.30;
    private static final double WEIGHT_WATER      = 0.20;
    private static final double WEIGHT_POLLUTION  = 0.30;
    private static final double WEIGHT_VEGETATION = 0.20;

    // Danger zone thresholds (values outside these ranges add stress).
    private static final double DANGER_SOIL_LOW        = 20.0;
    private static final double DANGER_POLLUTION_HIGH  = 70.0;
    private static final double DANGER_VEGETATION_LOW  = 10.0;
    private static final double DANGER_WATER_LOW       = 20.0;

    private static final double STRESS_PER_DANGER      = 2.0;
    private static final double STRESS_DECAY_PER_TICK  = 0.5;
    private static final double RESILIENCE_GROWTH_RATE = 0.02;
    private static final double RESILIENCE_DRAIN_RATE  = 0.01;
    private static final double CHANGE_THRESHOLD       = 0.01;

    private static final ModuleMetadata METADATA = new ModuleMetadata(
            MODULE_ID,
            "Ecosystem",
            "1.0.0",
            "Computes composite ecosystem health and manages long-term stress and resilience.",
            "1",
            List.of("pollution", "soil", "vegetation"),
            List.of(),
            true,
            true,
            false);

    @Override
    public String getModuleId() { return MODULE_ID; }

    @Override
    public ModuleMetadata getMetadata() { return METADATA; }

    @Override
    public void initialize(ModuleContext context) {
        if (context == null) throw new IllegalArgumentException("context must not be null");
    }

    @Override
    public void onServerStarted(ServerContext context) {}

    @Override
    public void createDefaultRegionData(Region region) {
        if (region == null) throw new IllegalArgumentException("region must not be null");
        if (!region.getModuleData().contains(MODULE_ID)) {
            region.getModuleData().put(MODULE_ID, EcosystemRegionData.defaults());
        }
    }

    @Override
    public ModuleUpdateResult updateRegion(RegionUpdateContext context) {
        if (context == null) throw new IllegalArgumentException("context must not be null");

        Region region = context.getRegion();
        EcosystemRegionData data = region.getModuleData()
                .get(MODULE_ID, EcosystemRegionData.class)
                .orElseGet(EcosystemRegionData::defaults);
        RegionMetrics metrics = region.getMetrics();

        double prevHealth          = metrics.getEcosystemHealth();
        double prevRecoveryPressure = metrics.getRecoveryPressure();

        // --- Composite health ---
        double health = metrics.getSoilQuality()       * WEIGHT_SOIL
                      + metrics.getWaterQuality()      * WEIGHT_WATER
                      + (100.0 - metrics.getPollutionScore()) * WEIGHT_POLLUTION
                      + metrics.getVegetationPressure() * WEIGHT_VEGETATION;
        health = Math.max(0.0, Math.min(100.0, health));

        // --- Stress accumulation ---
        int dangerCount = 0;
        if (metrics.getSoilQuality()       < DANGER_SOIL_LOW)       dangerCount++;
        if (metrics.getPollutionScore()    > DANGER_POLLUTION_HIGH)  dangerCount++;
        if (metrics.getVegetationPressure() < DANGER_VEGETATION_LOW) dangerCount++;
        if (metrics.getWaterQuality()      < DANGER_WATER_LOW)       dangerCount++;

        double newStress;
        if (dangerCount > 0) {
            newStress = data.getStress() + dangerCount * STRESS_PER_DANGER;
        } else {
            newStress = Math.max(0.0, data.getStress() - STRESS_DECAY_PER_TICK);
        }
        data.setStress(newStress);

        // --- Resilience trend ---
        if (health > 60.0) {
            data.setResilience(data.getResilience() + RESILIENCE_GROWTH_RATE);
        } else {
            data.setResilience(data.getResilience() - RESILIENCE_DRAIN_RATE);
        }

        // --- Recovery rate: high resilience and low stress produce fast recovery ---
        double recoveryRate = data.getResilience() * (1.0 - data.getStress() / 100.0) * 0.10 + 1.0;
        data.setRecoveryRate(Math.min(100.0, recoveryRate));

        data.setEcosystemHealth(health);

        // --- Summary metrics ---
        metrics.setEcosystemHealth(health);
        // Recovery pressure is high when the ecosystem is suffering and needs attention.
        double recoveryPressure = Math.max(0.0, (100.0 - health) + data.getStress() * 0.30);
        metrics.setRecoveryPressure(Math.min(100.0, recoveryPressure));

        region.getModuleData().put(MODULE_ID, data);

        boolean changed =
                Math.abs(metrics.getEcosystemHealth()   - prevHealth)          > CHANGE_THRESHOLD
             || Math.abs(metrics.getRecoveryPressure()  - prevRecoveryPressure) > CHANGE_THRESHOLD;

        return changed ? ModuleUpdateResult.changed() : ModuleUpdateResult.noChange();
    }

    @Override
    public void onLivingWorldEvent(LivingWorldEvent event) {}

    @Override
    public void saveModuleData(PersistenceWriter writer) {}

    @Override
    public void loadModuleData(PersistenceReader reader) {}

    @Override
    public void shutdown() {}
}
