package com.livingworld.modules.soil;

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;

/**
 * Simulates soil health dynamics and writes {@link RegionMetrics#getSoilQuality()}.
 *
 * <p>This module runs after {@link com.livingworld.modules.pollution.PollutionModule}
 * so it reads the current-tick pollution score when deciding contamination accumulation.
 *
 * <h3>Per-tick rules</h3>
 * <ol>
 *   <li>High pollution causes contamination to accumulate in the soil.
 *   <li>Contamination steadily degrades fertility.
 *   <li>Good vegetation cover promotes fertility recovery and resists erosion.
 *   <li>Low fertility with low vegetation accelerates erosion.
 *   <li>Soil quality is computed as a weighted score of fertility, contamination, and erosion.
 * </ol>
 */
public final class SoilModule implements SimulationModule {

    public static final String MODULE_ID = "soil";

    /** Pollution score above which contamination begins accumulating per tick. */
    private static final double POLLUTION_CONTAMINATION_THRESHOLD = 10.0;
    /** Fraction of excess pollution score that becomes contamination per tick. */
    private static final double POLLUTION_TO_CONTAMINATION_RATE   = 0.003;
    /** Fertility reduction per unit of contamination per tick. */
    private static final double CONTAMINATION_FERTILITY_DRAIN      = 0.005;
    /** Vegetation pressure threshold for fertility recovery to kick in. */
    private static final double VEGETATION_RECOVERY_THRESHOLD     = 40.0;
    /** Fertility gained per unit of excess vegetation pressure per tick. */
    private static final double VEGETATION_RECOVERY_RATE          = 0.002;
    /** Fertility level below which erosion increases. */
    private static final double EROSION_FERTILITY_THRESHOLD       = 30.0;
    /** Erosion increase per tick when fertility is low. */
    private static final double EROSION_INCREASE_RATE             = 0.05;
    /** Erosion reduction per tick when vegetation is good. */
    private static final double EROSION_VEGETATION_RESISTANCE     = 0.03;
    private static final double CHANGE_THRESHOLD                  = 0.01;

    private static final ModuleMetadata METADATA = new ModuleMetadata(
            MODULE_ID,
            "Soil",
            "1.0.0",
            "Simulates soil fertility, contamination, and erosion dynamics.",
            "1",
            List.of("pollution"),
            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, SoilRegionData.defaults());
        }
    }

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

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

        double prevSoilQuality = metrics.getSoilQuality();

        // Pollution causes contamination to build up in the soil.
        if (metrics.getPollutionScore() > POLLUTION_CONTAMINATION_THRESHOLD) {
            double excess = metrics.getPollutionScore() - POLLUTION_CONTAMINATION_THRESHOLD;
            data.setContamination(data.getContamination() + excess * POLLUTION_TO_CONTAMINATION_RATE);
        }

        // Contamination steadily drains fertility.
        data.setFertility(data.getFertility()
                - data.getContamination() * CONTAMINATION_FERTILITY_DRAIN);

        // Good vegetation cover promotes fertility recovery.
        if (metrics.getVegetationPressure() > VEGETATION_RECOVERY_THRESHOLD) {
            double excess = metrics.getVegetationPressure() - VEGETATION_RECOVERY_THRESHOLD;
            data.setFertility(data.getFertility() + excess * VEGETATION_RECOVERY_RATE);
        }

        // Low fertility accelerates erosion; good vegetation cover slows it.
        if (data.getFertility() < EROSION_FERTILITY_THRESHOLD) {
            data.setErosion(data.getErosion() + EROSION_INCREASE_RATE);
        }
        if (metrics.getVegetationPressure() > VEGETATION_RECOVERY_THRESHOLD) {
            data.setErosion(data.getErosion() - EROSION_VEGETATION_RESISTANCE);
        }

        // Soil quality: fertility is the main driver, penalised by contamination and erosion.
        double soilQuality = Math.max(0.0, Math.min(100.0,
                data.getFertility()
                - data.getContamination() * 0.40
                - data.getErosion()       * 0.30));
        metrics.setSoilQuality(soilQuality);

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

        boolean changed = Math.abs(metrics.getSoilQuality() - prevSoilQuality) > 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() {}
}
