package com.cameleer.server.app.storage;

import com.cameleer.common.model.ApplicationConfig;
import com.cameleer.server.app.AbstractPostgresIT;
import com.cameleer.server.core.admin.AppSettings;
import com.cameleer.server.core.admin.AppSettingsRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Verifies that the two per-app-per-env Postgres tables isolate data correctly between
 * environments — writing to (app=X, env=dev) must not affect reads for (app=X, env=prod).
 * Regression test for the pre-1.0 env-scoping gap (plans/2026-04-16-environment-scoping.md).
 */
class ConfigEnvIsolationIT extends AbstractPostgresIT {

    @Autowired PostgresApplicationConfigRepository configRepo;
    @Autowired AppSettingsRepository settingsRepo;
    @Autowired ObjectMapper objectMapper;

    @Test
    void applicationConfig_isolatesByEnvironment() {
        ApplicationConfig dev = new ApplicationConfig();
        dev.setSamplingRate(0.5);
        dev.setApplicationLogLevel("DEBUG");
        configRepo.save("order-svc", "dev", dev, "test");

        ApplicationConfig prod = new ApplicationConfig();
        prod.setSamplingRate(0.01);
        prod.setApplicationLogLevel("WARN");
        configRepo.save("order-svc", "prod", prod, "test");

        ApplicationConfig readDev = configRepo.findByApplicationAndEnvironment("order-svc", "dev")
                .orElseThrow();
        assertThat(readDev.getEnvironment()).isEqualTo("dev");
        assertThat(readDev.getSamplingRate()).isEqualTo(0.5);
        assertThat(readDev.getApplicationLogLevel()).isEqualTo("DEBUG");

        ApplicationConfig readProd = configRepo.findByApplicationAndEnvironment("order-svc", "prod")
                .orElseThrow();
        assertThat(readProd.getEnvironment()).isEqualTo("prod");
        assertThat(readProd.getSamplingRate()).isEqualTo(0.01);
        assertThat(readProd.getApplicationLogLevel()).isEqualTo("WARN");

        assertThat(configRepo.findByApplicationAndEnvironment("order-svc", "staging")).isEmpty();
    }

    @Test
    void applicationConfig_saveReplacesOnlySameEnv() {
        ApplicationConfig original = new ApplicationConfig();
        original.setSamplingRate(0.5);
        configRepo.save("svc", "dev", original, "alice");

        ApplicationConfig otherEnv = new ApplicationConfig();
        otherEnv.setSamplingRate(0.1);
        configRepo.save("svc", "prod", otherEnv, "alice");

        ApplicationConfig updated = new ApplicationConfig();
        updated.setSamplingRate(0.9);
        configRepo.save("svc", "dev", updated, "bob");

        assertThat(configRepo.findByApplicationAndEnvironment("svc", "dev").orElseThrow()
                .getSamplingRate()).isEqualTo(0.9);
        assertThat(configRepo.findByApplicationAndEnvironment("svc", "prod").orElseThrow()
                .getSamplingRate()).isEqualTo(0.1);
    }

    @Test
    void applicationConfig_findByEnvironment_excludesOtherEnvs() {
        // Use a unique app-slug prefix so this test's rows don't collide with
        // the other tests in this class — they all share a Testcontainers
        // Postgres and @Transactional rollback isn't wired up here.
        ApplicationConfig a = new ApplicationConfig();
        a.setSamplingRate(1.0);
        configRepo.save("fbe-a", "dev", a, "test");
        configRepo.save("fbe-b", "dev", a, "test");
        configRepo.save("fbe-a", "prod", a, "test");

        assertThat(configRepo.findByEnvironment("dev"))
                .extracting(ApplicationConfig::getApplication)
                .contains("fbe-a", "fbe-b")
                .doesNotContain("fbe-a-prod-sentinel");
        assertThat(configRepo.findByEnvironment("prod"))
                .extracting(ApplicationConfig::getApplication)
                .contains("fbe-a")
                .doesNotContain("fbe-b");
    }

    @Test
    void appSettings_isolatesByEnvironment() {
        settingsRepo.save(new AppSettings(
                "svc", "dev", 500, 1.0, 5.0, 99.0, 95.0, null, null));
        settingsRepo.save(new AppSettings(
                "svc", "prod", 100, 0.5, 2.0, 99.9, 99.5, null, null));

        AppSettings readDev = settingsRepo.findByApplicationAndEnvironment("svc", "dev").orElseThrow();
        assertThat(readDev.slaThresholdMs()).isEqualTo(500);
        assertThat(readDev.environment()).isEqualTo("dev");

        AppSettings readProd = settingsRepo.findByApplicationAndEnvironment("svc", "prod").orElseThrow();
        assertThat(readProd.slaThresholdMs()).isEqualTo(100);
        assertThat(readProd.environment()).isEqualTo("prod");

        assertThat(settingsRepo.findByApplicationAndEnvironment("svc", "staging")).isEmpty();
    }

    @Test
    void appSettings_delete_scopedToSingleEnv() {
        settingsRepo.save(new AppSettings(
                "svc", "dev", 500, 1.0, 5.0, 99.0, 95.0, null, null));
        settingsRepo.save(new AppSettings(
                "svc", "prod", 100, 0.5, 2.0, 99.9, 99.5, null, null));

        settingsRepo.delete("svc", "dev");

        assertThat(settingsRepo.findByApplicationAndEnvironment("svc", "dev")).isEmpty();
        assertThat(settingsRepo.findByApplicationAndEnvironment("svc", "prod")).isPresent();
    }
}
