package com.livingworld.modules.atmosphere;

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.modules.pollution.PollutionModule;
import com.livingworld.modules.pollution.PollutionRegionData;
import com.livingworld.modules.soil.SoilModule;
import com.livingworld.modules.soil.SoilRegionData;
import com.livingworld.modules.vegetation.VegetationModule;
import com.livingworld.modules.vegetation.VegetationRegionData;
import com.livingworld.modules.water.WaterModule;
import com.livingworld.modules.water.WaterRegionData;
import com.livingworld.regions.Region;

import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;

/**
 * Drives per-region weather from ecosystem state and applies atmospheric feedback
 * to other simulation layers.
 *
 * <h3>Pipeline position</h3>
 * Runs last (after WorldEffects) so ecosystem health and pollution scores are final
 * before weather targets are computed.
 *
 * <h3>Per-cycle rules</h3>
 * <ol>
 *   <li>Tree canopy scrubs air pollution: {@code airPollution -= treePressure × TREE_SCRUB_RATE}.
 *   <li>Target rain level is derived from ecosystem health (healthy = wet, degraded = drought).
 *       Global Minecraft rain adds a small bias so natural weather still matters.
 *   <li>Target thunder level is derived from pollution (polluted = stormy).
 *   <li>Rain and thunder smoothly interpolate toward their targets each cycle.
 *   <li>Rain fills water availability; drought raises drought risk.
 *   <li>High thunder + high pollution triggers acid rain (drains soil fertility).
 * </ol>
 */
public final class AtmosphereModule implements SimulationModule {

    public static final String MODULE_ID = "atmosphere";

    // Tree scrub: treePressure (0-100) × rate removed from air pollution per cycle.
    private static final double TREE_SCRUB_RATE       = 0.003;

    // Rain target ceiling when ecosystem is pristine (no global rain).
    private static final double MAX_RAIN_FROM_ECO     = 0.85;
    // Additive bias when Minecraft's overworld is actually raining.
    private static final double GLOBAL_RAIN_BIAS      = 0.15;

    // Smoothing rates — weather changes slowly, not instantly.
    private static final double RAIN_SMOOTHING        = 0.05;
    private static final double THUNDER_SMOOTHING     = 0.03;

    // Rain < this → drought conditions worsen each cycle.
    private static final double DROUGHT_THRESHOLD     = 0.2;
    private static final double RAIN_MOISTURE_GAIN    = 0.3;
    private static final double RAIN_DROUGHT_RELIEF   = 0.15;
    private static final double DRY_DROUGHT_INCREASE  = 0.05;

    // Acid rain fires when BOTH thunder and pollution exceed these thresholds.
    private static final double ACID_THUNDER_THRESHOLD = 0.4;
    private static final double ACID_POLL_THRESHOLD    = 20.0;
    private static final double ACID_FERTILITY_DRAIN   = 0.0005;

    private static final ModuleMetadata METADATA = new ModuleMetadata(
            MODULE_ID,
            "Atmosphere",
            "1.0.0",
            "Per-region weather driven by ecosystem health and pollution. Trees scrub air pollution.",
            "9",
            List.of(),
            List.of(),
            true,
            true,
            false);

    private final BooleanSupplier globalRaining;
    private final Supplier<Season> currentSeason;
    private final DoubleSupplier   globalWarmingPenalty;

    public AtmosphereModule(BooleanSupplier globalRaining, Supplier<Season> currentSeason,
                            DoubleSupplier globalWarmingPenalty) {
        this.globalRaining        = globalRaining;
        this.currentSeason        = currentSeason;
        this.globalWarmingPenalty = globalWarmingPenalty;
    }

    @Override public String getModuleId()               { return MODULE_ID; }
    @Override public ModuleMetadata getMetadata()       { return METADATA; }
    @Override public void initialize(ModuleContext ctx) {}
    @Override public void onServerStarted(ServerContext ctx) {}
    @Override public void onLivingWorldEvent(LivingWorldEvent event) {}
    @Override public void saveModuleData(PersistenceWriter w) {}
    @Override public void loadModuleData(PersistenceReader r) {}
    @Override public void shutdown() {}

    @Override
    public void createDefaultRegionData(Region region) {
        if (!region.getModuleData().contains(MODULE_ID)) {
            region.getModuleData().put(MODULE_ID, AtmosphereRegionData.defaults());
        }
    }

    @Override
    public ModuleUpdateResult updateRegion(RegionUpdateContext context) {
        Region region = context.getRegion();

        AtmosphereRegionData data = region.getModuleData()
                .get(MODULE_ID, AtmosphereRegionData.class)
                .orElseGet(AtmosphereRegionData::defaults);

        // 1. Vegetation scrubs air pollution.
        VegetationRegionData veg = region.getModuleData()
                .get(VegetationModule.MODULE_ID, VegetationRegionData.class)
                .orElse(null);
        PollutionRegionData pollution = region.getModuleData()
                .get(PollutionModule.MODULE_ID, PollutionRegionData.class)
                .orElse(null);

        if (veg != null && pollution != null) {
            double scrub = veg.getTreePressure() * TREE_SCRUB_RATE;
            pollution.addPollution(-scrub, 0.0, 0.0);
            region.getModuleData().put(PollutionModule.MODULE_ID, pollution);
            // Recompute pollution score now that trees have scrubbed the air.
            double pollScore = pollution.getAirPollution()    * 0.40
                             + pollution.getGroundPollution() * 0.35
                             + pollution.getWaterPollution()  * 0.25;
            region.getMetrics().setPollutionScore(pollScore);
        }

        // 2. Compute targets.
        double ecosystemHealth = region.getMetrics().getEcosystemHealth();
        double pollScore       = region.getMetrics().getPollutionScore();
        boolean mcRaining      = globalRaining.getAsBoolean();

        double seasonMod  = currentSeason.get().rainModifier();
        double targetRain    = clamp01(ecosystemHealth / 100.0 * MAX_RAIN_FROM_ECO
                                       + seasonMod
                                       - globalWarmingPenalty.getAsDouble()
                                       + (mcRaining ? GLOBAL_RAIN_BIAS : 0.0));
        double targetThunder = clamp01(pollScore / 100.0 * 0.8);

        // 3. Smooth toward targets.
        double rain    = data.getRainLevel()    + (targetRain    - data.getRainLevel())    * RAIN_SMOOTHING;
        double thunder = data.getThunderLevel() + (targetThunder - data.getThunderLevel()) * THUNDER_SMOOTHING;
        data.setRainLevel(rain);
        data.setThunderLevel(thunder);

        // 4. Apply rain effects to water availability and drought risk.
        WaterRegionData water = region.getModuleData()
                .get(WaterModule.MODULE_ID, WaterRegionData.class)
                .orElse(null);
        if (water != null) {
            water.setWaterAvailability(
                    Math.min(100, water.getWaterAvailability() + rain * RAIN_MOISTURE_GAIN));
            if (rain < DROUGHT_THRESHOLD) {
                water.setDroughtRisk(
                        Math.min(100, water.getDroughtRisk() + DRY_DROUGHT_INCREASE * (1.0 - rain)));
            } else {
                water.setDroughtRisk(
                        Math.max(0, water.getDroughtRisk() - RAIN_DROUGHT_RELIEF * rain));
            }

            // 5. Acid rain: stormy + polluted atmosphere corrodes soil and water.
            if (thunder > ACID_THUNDER_THRESHOLD && pollScore > ACID_POLL_THRESHOLD) {
                double acidStrength = thunder * (pollScore - ACID_POLL_THRESHOLD) * ACID_FERTILITY_DRAIN;
                SoilRegionData soil = region.getModuleData()
                        .get(SoilModule.MODULE_ID, SoilRegionData.class)
                        .orElse(null);
                if (soil != null) {
                    soil.setFertility(Math.max(0, soil.getFertility() - acidStrength));
                    soil.setContamination(Math.min(100, soil.getContamination() + acidStrength * 0.5));
                    region.getModuleData().put(SoilModule.MODULE_ID, soil);
                }
                water.setWaterAvailability(
                        Math.max(0, water.getWaterAvailability() - acidStrength * 0.3));
            }
            region.getModuleData().put(WaterModule.MODULE_ID, water);
        }

        region.getModuleData().put(MODULE_ID, data);
        return ModuleUpdateResult.changed();
    }

    private static double clamp01(double v) { return Math.min(1.0, Math.max(0.0, v)); }
}
