package com.livingworld.modules.recovery;

import com.livingworld.config.EcosystemTuning;
import com.livingworld.core.services.CoreServices;
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;

/**
 * Manages ecological succession: the gradual progression of a region from bare
 * ground through grassland, scrubland, and young woodland to mature forest —
 * and the regression back toward bare ground when conditions deteriorate.
 *
 * <p>This module runs near the end of the pipeline (after pollution, soil,
 * water, vegetation, and resources have updated) so that succession decisions
 * are made against fully current metrics.
 *
 * <h3>Advancement</h3>
 * Each tick that metrics meet a stage's thresholds, {@code recoveryProgress}
 * increases by the ecosystem's {@code recoveryPressure}-derived rate. When
 * it reaches 100, the region advances one succession stage.
 *
 * <h3>Regression</h3>
 * Each tick that metrics fall badly below the current stage's minimums,
 * {@code damageAccumulation} increases. At 70 accumulated damage the region
 * regresses one stage.
 *
 * <h3>Recovery pressure metric</h3>
 * The module modifies {@link RegionMetrics#getRecoveryPressure()} to reflect
 * how far the region is from mature forest (EcosystemModule also writes this;
 * this module's write takes precedence because it runs later).
 */
public final class RecoveryModule implements SimulationModule {

    public static final String MODULE_ID = "recovery";

    /** Extra recovery progress per point of ecosystemHealth above 50. */
    private static final double HEALTH_PROGRESS_BONUS = 0.05;
    private static final double CHANGE_THRESHOLD      = 0.01;

    private EcosystemTuning tuning = new EcosystemTuning();

    private static final ModuleMetadata METADATA = new ModuleMetadata(
            MODULE_ID,
            "Recovery & Succession",
            "1.0.0",
            "Manages ecological succession stages from barren ground to mature forest.",
            "1",
            List.of("pollution", "soil", "vegetation"),
            List.of("ecosystem"),
            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");
        if (context.hasService(CoreServices.TUNING)) this.tuning = context.getService(CoreServices.TUNING);
    }

    @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, RecoveryRegionData.defaults());
        }
    }

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

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

        SuccessionStage stageBefore = data.getSuccessionStage();
        double progressBefore = data.getRecoveryProgress();

        double soil  = metrics.getSoilQuality();
        double poll  = metrics.getPollutionScore();
        double veg   = metrics.getVegetationPressure();

        if (data.getSuccessionStage().conditionsMetForAdvancement(soil, poll, veg)) {
            // Conditions are good: advance recovery progress.
            double progressGain = tuning.getBaseProgressPerTick();
            if (metrics.getEcosystemHealth() > 50.0) {
                progressGain += (metrics.getEcosystemHealth() - 50.0) * HEALTH_PROGRESS_BONUS;
            }
            data.advanceProgress(progressGain, soil, poll, veg);

            // Damage decays passively when conditions are good. Use 4-arg constructor to
            // preserve the dynamic succession cap set by applyDynamicCapUpdate().
            data = new RecoveryRegionData(
                    data.getSuccessionStage(),
                    data.getRecoveryProgress(),
                    Math.max(0.0, data.getDamageAccumulation() * (1.0 - tuning.getDamageDecayRate())),
                    data.getMaxSuccessionStage());

        } else if (data.getSuccessionStage().conditionsMissedForRegression(soil, poll, veg)) {
            // Conditions are bad: accumulate damage toward regression.
            data.accumulateDamage(tuning.getDamagePerBadTick(), soil, poll, veg);
        }

        // Recovery pressure reflects distance from mature forest.
        int stagesFromPeak = SuccessionStage.MATURE_FOREST.ordinal()
                           - data.getSuccessionStage().ordinal();
        double recoveryPressure = Math.min(100.0, stagesFromPeak * 20.0
                + data.getDamageAccumulation() * 0.30);
        metrics.setRecoveryPressure(recoveryPressure);

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

        boolean stageChanged = data.getSuccessionStage() != stageBefore;
        boolean progressChanged = Math.abs(data.getRecoveryProgress() - progressBefore)
                                  > CHANGE_THRESHOLD;
        return (stageChanged || progressChanged)
                ? 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() {}
}
