package com.livingworld.regions;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Comprehensive tests for {@link RegionMetrics}.
 */
class RegionMetricsTest {

    // ------------------------------------------------------------------
    // Default Values Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Default Values")
    class DefaultValues {

        @Test
        @DisplayName("defaults() returns correct ecosystem health")
        void defaultsEcosystemHealth() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(60, metrics.getEcosystemHealth(), 0.001);
        }

        @Test
        @DisplayName("defaults() returns correct pollution score")
        void defaultsPollutionScore() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(0, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("defaults() returns correct soil quality")
        void defaultsSoilQuality() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(60, metrics.getSoilQuality(), 0.001);
        }

        @Test
        @DisplayName("defaults() returns correct water quality")
        void defaultsWaterQuality() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(60, metrics.getWaterQuality(), 0.001);
        }

        @Test
        @DisplayName("defaults() returns correct vegetation pressure")
        void defaultsVegetationPressure() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(50, metrics.getVegetationPressure(), 0.001);
        }

        @Test
        @DisplayName("defaults() returns correct resource depletion")
        void defaultsResourceDepletion() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(0, metrics.getResourceDepletion(), 0.001);
        }

        @Test
        @DisplayName("defaults() returns correct recovery pressure")
        void defaultsRecoveryPressure() {
            RegionMetrics metrics = RegionMetrics.defaults();
            assertEquals(50, metrics.getRecoveryPressure(), 0.001);
        }

        @Test
        @DisplayName("default constructor initializes all fields to 0")
        void defaultConstructorAllZeros() {
            RegionMetrics metrics = new RegionMetrics();
            assertEquals(0, metrics.getEcosystemHealth(), 0.001);
            assertEquals(0, metrics.getPollutionScore(), 0.001);
            assertEquals(0, metrics.getSoilQuality(), 0.001);
            assertEquals(0, metrics.getWaterQuality(), 0.001);
            assertEquals(0, metrics.getVegetationPressure(), 0.001);
            assertEquals(0, metrics.getResourceDepletion(), 0.001);
            assertEquals(0, metrics.getRecoveryPressure(), 0.001);
        }
    }

    // ------------------------------------------------------------------
    // Getter/Setter Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Getter and Setter Clamping")
    class GetterSetterClamping {

        @Test
        @DisplayName("setter clamps values above 100 to 100")
        void setterClampsAboveMax() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(150);
            assertEquals(100, metrics.getEcosystemHealth(), 0.001);
        }

        @Test
        @DisplayName("setter clamps values below 0 to 0")
        void setterClampsBelowMin() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setPollutionScore(-50);
            assertEquals(0, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("setter accepts exact boundary values")
        void setterAcceptsBoundaries() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setSoilQuality(0);
            assertEquals(0, metrics.getSoilQuality(), 0.001);
            metrics.setSoilQuality(100);
            assertEquals(100, metrics.getSoilQuality(), 0.001);
        }

        @Test
        @DisplayName("setter accepts normal values")
        void setterAcceptsNormalValues() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setWaterQuality(42.5);
            assertEquals(42.5, metrics.getWaterQuality(), 0.001);
        }

        @Test
        @DisplayName("all setters clamp correctly")
        void allSettersClamp() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setVegetationPressure(-10);
            assertEquals(0, metrics.getVegetationPressure(), 0.001);

            metrics.setResourceDepletion(200);
            assertEquals(100, metrics.getResourceDepletion(), 0.001);

            metrics.setRecoveryPressure(-5);
            assertEquals(0, metrics.getRecoveryPressure(), 0.001);
        }
    }

    // ------------------------------------------------------------------
    // Normalize Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Normalize")
    class NormalizeTests {

        @Test
        @DisplayName("normalize() clamps values above 100")
        void normalizeClampsAboveMax() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(150);
            metrics.setPollutionScore(-20);
            metrics.normalize();
            assertEquals(100, metrics.getEcosystemHealth(), 0.001);
            assertEquals(0, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("normalize() clamps values below 0")
        void normalizeClampsBelowMin() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(-50);
            metrics.setPollutionScore(200);
            metrics.normalize();
            assertEquals(0, metrics.getEcosystemHealth(), 0.001);
            assertEquals(100, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("normalize() leaves valid values unchanged")
        void normalizeLeavesValidUnchanged() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(50);
            metrics.setPollutionScore(30);
            metrics.normalize();
            assertEquals(50, metrics.getEcosystemHealth(), 0.001);
            assertEquals(30, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("normalize() returns this for chaining")
        void normalizeReturnsThis() {
            RegionMetrics metrics = new RegionMetrics();
            RegionMetrics result = metrics.normalize();
            assertTrue(result == metrics, "normalize() should return the same instance");
        }
    }

    // ------------------------------------------------------------------
    // Copy Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Copy")
    class CopyTests {

        @Test
        @DisplayName("copy() returns independent instance")
        void copyReturnsIndependentInstance() {
            RegionMetrics original = new RegionMetrics();
            original.setEcosystemHealth(80);
            original.setPollutionScore(30);

            RegionMetrics copy = original.copy();

            assertEquals(80, copy.getEcosystemHealth(), 0.001);
            assertEquals(30, copy.getPollutionScore(), 0.001);

            // Modify the copy
            copy.setEcosystemHealth(90);

            // Original should be unchanged
            assertEquals(80, original.getEcosystemHealth(), 0.001);
        }

        @Test
        @DisplayName("copy() returns a different object")
        void copyReturnsDifferentObject() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setSoilQuality(75);
            RegionMetrics copy = metrics.copy();
            assertNotSame(metrics, copy);
        }

        @Test
        @DisplayName("copy() copies all fields")
        void copyAllFields() {
            RegionMetrics original = new RegionMetrics();
            original.setEcosystemHealth(10);
            original.setPollutionScore(20);
            original.setSoilQuality(30);
            original.setWaterQuality(40);
            original.setVegetationPressure(50);
            original.setResourceDepletion(60);
            original.setRecoveryPressure(70);

            RegionMetrics copy = original.copy();

            assertEquals(original.getEcosystemHealth(), copy.getEcosystemHealth(), 0.001);
            assertEquals(original.getPollutionScore(), copy.getPollutionScore(), 0.001);
            assertEquals(original.getSoilQuality(), copy.getSoilQuality(), 0.001);
            assertEquals(original.getWaterQuality(), copy.getWaterQuality(), 0.001);
            assertEquals(original.getVegetationPressure(), copy.getVegetationPressure(), 0.001);
            assertEquals(original.getResourceDepletion(), copy.getResourceDepletion(), 0.001);
            assertEquals(original.getRecoveryPressure(), copy.getRecoveryPressure(), 0.001);
        }
    }

    // ------------------------------------------------------------------
    // ApplyDelta Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Apply Delta")
    class ApplyDeltaTests {

        @Test
        @DisplayName("applyDelta() modifies all fields correctly")
        void applyDeltaAllFields() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(50);
            metrics.setPollutionScore(20);
            metrics.setSoilQuality(80);

            metrics.applyDelta(10, -5, 15, 0, 0, 0, 0);

            assertEquals(60, metrics.getEcosystemHealth(), 0.001);
            assertEquals(15, metrics.getPollutionScore(), 0.001);
            assertEquals(95, metrics.getSoilQuality(), 0.001);
        }

        @Test
        @DisplayName("applyDelta() clamps results above max")
        void applyDeltaClampsAboveMax() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(95);
            metrics.applyDelta(20, 0, 0, 0, 0, 0, 0);
            assertEquals(100, metrics.getEcosystemHealth(), 0.001);
        }

        @Test
        @DisplayName("applyDelta() clamps results below min")
        void applyDeltaClampsBelowMin() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setPollutionScore(5);
            metrics.applyDelta(-10, 0, 0, 0, 0, 0, 0);
            assertEquals(0, metrics.getEcosystemHealth(), 0.001);
        }

        @Test
        @DisplayName("applyDelta() returns this for chaining")
        void applyDeltaReturnsThis() {
            RegionMetrics metrics = new RegionMetrics();
            RegionMetrics result = metrics.applyDelta(0, 0, 0, 0, 0, 0, 0);
            assertTrue(result == metrics);
        }

        @Test
        @DisplayName("applyEcosystemHealthDelta() modifies only ecosystem health")
        void applySingleDelta() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(50);
            metrics.setPollutionScore(30);
            metrics.applyEcosystemHealthDelta(10);
            assertEquals(60, metrics.getEcosystemHealth(), 0.001);
            assertEquals(30, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("applyPollutionScoreDelta() clamps correctly")
        void applySingleDeltaClamps() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setPollutionScore(95);
            metrics.applyPollutionScoreDelta(20);
            assertEquals(100, metrics.getPollutionScore(), 0.001);
        }

        @Test
        @DisplayName("applySoilQualityDelta() clamps below min")
        void applySingleDeltaClampsBelow() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setSoilQuality(5);
            metrics.applySoilQualityDelta(-10);
            assertEquals(0, metrics.getSoilQuality(), 0.001);
        }

        @Test
        @DisplayName("applyWaterQualityDelta() works correctly")
        void applyWaterQualityDelta() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setWaterQuality(50);
            metrics.applyWaterQualityDelta(25);
            assertEquals(75, metrics.getWaterQuality(), 0.001);
        }

        @Test
        @DisplayName("applyVegetationPressureDelta() works correctly")
        void applyVegetationPressureDelta() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setVegetationPressure(30);
            metrics.applyVegetationPressureDelta(-10);
            assertEquals(20, metrics.getVegetationPressure(), 0.001);
        }

        @Test
        @DisplayName("applyResourceDepletionDelta() works correctly")
        void applyResourceDepletionDelta() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setResourceDepletion(40);
            metrics.applyResourceDepletionDelta(60);
            assertEquals(100, metrics.getResourceDepletion(), 0.001);
        }

        @Test
        @DisplayName("applyRecoveryPressureDelta() works correctly")
        void applyRecoveryPressureDelta() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setRecoveryPressure(25);
            metrics.applyRecoveryPressureDelta(-15);
            assertEquals(10, metrics.getRecoveryPressure(), 0.001);
        }

        @Test
        @DisplayName("individual apply methods return this for chaining")
        void individualApplyMethodsReturnThis() {
            RegionMetrics metrics = new RegionMetrics();
            assertTrue(metrics.applyEcosystemHealthDelta(5) == metrics);
            assertTrue(metrics.applyPollutionScoreDelta(5) == metrics);
            assertTrue(metrics.applySoilQualityDelta(5) == metrics);
            assertTrue(metrics.applyWaterQualityDelta(5) == metrics);
            assertTrue(metrics.applyVegetationPressureDelta(5) == metrics);
            assertTrue(metrics.applyResourceDepletionDelta(5) == metrics);
            assertTrue(metrics.applyRecoveryPressureDelta(5) == metrics);
        }
    }

    // ------------------------------------------------------------------
    // Constants Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("Constants")
    class ConstantsTests {

        @Test
        @DisplayName("MIN_VALUE is 0")
        void minValue() {
            assertEquals(0, RegionMetrics.MIN_VALUE);
        }

        @Test
        @DisplayName("MAX_VALUE is 100")
        void maxValue() {
            assertEquals(100, RegionMetrics.MAX_VALUE);
        }
    }

    // ------------------------------------------------------------------
    // toString Tests
    // ------------------------------------------------------------------

    @Nested
    @DisplayName("toString")
    class ToStringTests {

        @Test
        @DisplayName("toString() includes all field names and values")
        void toStringIncludesAllFields() {
            RegionMetrics metrics = new RegionMetrics();
            metrics.setEcosystemHealth(60);
            metrics.setPollutionScore(10);
            String str = metrics.toString();

            assertTrue(str.contains("ecosystemHealth"));
            assertTrue(str.contains("pollutionScore"));
            assertTrue(str.contains("soilQuality"));
            assertTrue(str.contains("waterQuality"));
            assertTrue(str.contains("vegetationPressure"));
            assertTrue(str.contains("resourceDepletion"));
            assertTrue(str.contains("recoveryPressure"));
        }

        @Test
        @DisplayName("toString() starts with class name")
        void toStringStartsWithClassName() {
            RegionMetrics metrics = new RegionMetrics();
            assertTrue(metrics.toString().startsWith("RegionMetrics{"));
        }
    }
}