package com.magistr.app.config.tenant;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okhttp3.tls.HandshakeCertificates;
import okhttp3.tls.HeldCertificate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

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

class KubernetesTenantSecretUpdaterTest {

    private static final TypeReference<List<TenantConfig>> TENANT_LIST_TYPE = new TypeReference<>() {
    };

    private final ObjectMapper objectMapper = new ObjectMapper();

    @TempDir
    Path tempDirectory;

    @Test
    void readsCurrentSecretAndUsesConditionalPutWithoutPlainCredentials() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        HeldCertificate serverCertificate = serverCertificate(ca, "localhost");
        TenantConfig tenant = tenant("Тестовый университет", "test", "пароль-" + UUID.randomUUID());

        try (MockWebServer server = httpsServer(serverCertificate)) {
            server.enqueue(secretResponse("17", List.of()));
            server.enqueue(secretResponse("18", List.of(tenant)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(tenant);

            assertThat(receipt.persisted()).isTrue();
            assertThat(receipt.changed()).isTrue();
            assertThat(receipt.committedResourceVersion()).isEqualTo("18");
            assertThat(domains(receipt.previousTenants())).isEmpty();
            assertThat(domains(receipt.committedTenants())).containsExactly("test");

            RecordedRequest get = server.takeRequest(2, TimeUnit.SECONDS);
            RecordedRequest put = server.takeRequest(2, TimeUnit.SECONDS);
            assertThat(get).isNotNull();
            assertThat(get.getMethod()).isEqualTo("GET");
            assertThat(get.getPath()).isEqualTo("/api/v1/namespaces/magistr/secrets/tenants-secret");
            assertThat(get.getHeader("Authorization")).isEqualTo("Bearer test-service-account-token");

            assertThat(put).isNotNull();
            assertThat(put.getMethod()).isEqualTo("PUT");
            assertThat(put.getHeader("Content-Type")).startsWith("application/json");
            String body = put.getBody().readUtf8();
            assertThat(body).doesNotContain(tenant.getPassword());
            JsonNode update = objectMapper.readTree(body);
            assertThat(update.path("metadata").path("resourceVersion").asText()).isEqualTo("17");
            assertThat(update.path("data").path("other.key").asText()).isEqualTo("cHJlc2VydmU=");
            assertThat(domains(decodeTenants(update))).containsExactly("test");
        }
    }

    @Test
    void skipsPutWhenUpsertIsIdempotent() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig tenant = tenant("Университет", "same", "секрет");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("5", List.of(tenant)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(tenant);

            assertThat(receipt.persisted()).isTrue();
            assertThat(receipt.changed()).isFalse();
            assertThat(receipt.committedResourceVersion()).isEqualTo("5");
            assertThat(server.getRequestCount()).isEqualTo(1);
        }
    }

    @Test
    void removeTenantKeepsOtherCurrentTenants() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("7", List.of(alpha, beta)));
            server.enqueue(secretResponse("8", List.of(beta)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server);

            TenantSecretUpdateReceipt receipt = updater.removeTenant("alpha");

            assertThat(receipt.changed()).isTrue();
            assertThat(domains(receipt.previousTenants())).containsExactly("alpha", "beta");
            assertThat(domains(receipt.committedTenants())).containsExactly("beta");
            server.takeRequest(2, TimeUnit.SECONDS);
            RecordedRequest put = server.takeRequest(2, TimeUnit.SECONDS);
            assertThat(domains(decodeTenants(objectMapper.readTree(put.getBody().readUtf8()))))
                    .containsExactly("beta");
        }
    }

    @Test
    void retriesConflictAgainstFreshResourceVersion() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("1", List.of()));
            server.enqueue(new MockResponse().setResponseCode(409));
            server.enqueue(secretResponse("2", List.of(alpha)));
            server.enqueue(secretResponse("3", List.of(alpha, beta)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);

            assertThat(receipt.committedResourceVersion()).isEqualTo("3");
            assertThat(domains(receipt.previousTenants())).containsExactly("alpha");
            assertThat(domains(receipt.committedTenants())).containsExactly("alpha", "beta");
            assertThat(server.getRequestCount()).isEqualTo(4);

            server.takeRequest(2, TimeUnit.SECONDS);
            server.takeRequest(2, TimeUnit.SECONDS);
            server.takeRequest(2, TimeUnit.SECONDS);
            RecordedRequest finalPut = server.takeRequest(2, TimeUnit.SECONDS);
            JsonNode body = objectMapper.readTree(finalPut.getBody().readUtf8());
            assertThat(body.path("metadata").path("resourceVersion").asText()).isEqualTo("2");
            assertThat(domains(decodeTenants(body))).containsExactly("alpha", "beta");
        }
    }

    @Test
    void stopsAfterBoundedNumberOfConflicts() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            for (int attempt = 1; attempt <= 3; attempt++) {
                server.enqueue(secretResponse(String.valueOf(attempt), List.of()));
                server.enqueue(new MockResponse().setResponseCode(409));
            }
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(tenant("Тест", "test", "секрет")))
                    .isInstanceOf(TenantConfigPersistenceException.class)
                    .hasMessageContaining("конкурентных");
            assertThat(server.getRequestCount()).isEqualTo(6);
        }
    }

    @Test
    void reconcilesRetryablePutResponseWithoutCreatingUnsafeCompensationReceipt() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("20", List.of(alpha)));
            server.enqueue(new MockResponse().setResponseCode(503));
            server.enqueue(secretResponse("21", List.of(alpha, beta)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);

            assertThat(receipt.changed()).isFalse();
            assertThat(receipt.committedResourceVersion()).isEqualTo("21");
            assertThat(domains(receipt.committedTenants())).containsExactly("alpha", "beta");
            assertThat(updater.compensate(receipt))
                    .isEqualTo(TenantSecretCompensationResult.NOT_REQUIRED);
            assertThat(server.getRequestCount()).isEqualTo(3);
        }
    }

    @Test
    void reconcilesRequestTimeoutWithoutCreatingUnsafeCompensationReceipt() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("24", List.of(alpha)));
            server.enqueue(new MockResponse().setResponseCode(408));
            server.enqueue(secretResponse("25", List.of(alpha, beta)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);

            assertThat(receipt.persisted()).isTrue();
            assertThat(receipt.changed()).isFalse();
            assertThat(receipt.committedResourceVersion()).isEqualTo("25");
            assertThat(domains(receipt.committedTenants())).containsExactly("alpha", "beta");
            assertThat(server.getRequestCount()).isEqualTo(3);
        }
    }

    @Test
    void rejectsSuccessfulMutationResponseWithDifferentTenantContent() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");
        TenantConfig gamma = tenant("Гамма", "gamma", "секрет-c");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("26", List.of(alpha)));
            server.enqueue(secretResponse("27", List.of(alpha, gamma)));
            server.enqueue(secretResponse("27", List.of(alpha, gamma)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(beta))
                    .isInstanceOf(TenantConfigPersistenceException.class)
                    .hasMessageContaining("не подтвердил сохранённое содержимое");
            assertThat(server.getRequestCount()).isEqualTo(3);
        }
    }

    @Test
    void normalizesDomainsBeforeMergeAndRejectsCaseInsensitiveDuplicates() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig upperAlpha = tenant("Альфа", " ALPHA ", "секрет-a");
        TenantConfig lowerAlpha = tenant("Другая Альфа", "alpha", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("22", List.of(upperAlpha, lowerAlpha)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(tenant("Бета", "beta", "секрет-c")))
                    .isInstanceOf(TenantConfigPersistenceException.class)
                    .hasMessageContaining("повторяющиеся домены");
            assertThat(server.getRequestCount()).isEqualTo(1);
        }
    }

    @Test
    void failsClosedWhenTenantsKeyIsMissing() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        ObjectNode secret = objectMapper.createObjectNode();
        secret.put("apiVersion", "v1");
        secret.put("kind", "Secret");
        secret.putObject("metadata").put("resourceVersion", "23");
        secret.putObject("data");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(new MockResponse()
                    .setResponseCode(200)
                    .setHeader("Content-Type", "application/json")
                    .setBody(objectMapper.writeValueAsString(secret)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(tenant("Бета", "beta", "секрет")))
                    .isInstanceOf(TenantConfigPersistenceException.class)
                    .hasMessageContaining("data.tenants.json");
            assertThat(server.getRequestCount()).isEqualTo(1);
        }
    }

    @Test
    void terminalKubernetesErrorIsNotRetried() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(new MockResponse().setResponseCode(403));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(tenant("Бета", "beta", "секрет")))
                    .isInstanceOf(TenantConfigPersistenceException.class)
                    .hasMessageContaining("HTTP 403");
            assertThat(server.getRequestCount()).isEqualTo(1);
        }
    }

    @Test
    void twoConcurrentUpdatersPreserveBothMutations() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");
        CasDispatcher dispatcher = new CasDispatcher(List.of(), true);

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.setDispatcher(dispatcher);
            TestFiles files = testFiles(ca);
            KubernetesTenantSecretUpdater first = updater(files, server, 4, Duration.ZERO);
            KubernetesTenantSecretUpdater second = updater(files, server, 4, Duration.ZERO);
            ExecutorService executor = Executors.newFixedThreadPool(2);
            try {
                Future<TenantSecretUpdateReceipt> firstResult =
                        executor.submit(() -> first.upsertTenant(alpha));
                Future<TenantSecretUpdateReceipt> secondResult =
                        executor.submit(() -> second.upsertTenant(beta));

                assertThat(firstResult.get(10, TimeUnit.SECONDS).changed()).isTrue();
                assertThat(secondResult.get(10, TimeUnit.SECONDS).changed()).isTrue();
            } finally {
                executor.shutdownNow();
            }

            assertThat(domains(dispatcher.currentTenants())).containsExactly("alpha", "beta");
            assertThat(dispatcher.conflictCount()).isGreaterThanOrEqualTo(1);
        }
    }

    @Test
    void compensationRestoresOnlyVersionCommittedByOperation() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("30", List.of(alpha)));
            server.enqueue(secretResponse("31", List.of(alpha, beta)));
            server.enqueue(secretResponse("31", List.of(alpha, beta)));
            server.enqueue(secretResponse("32", List.of(alpha)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);
            TenantSecretCompensationResult result = updater.compensate(receipt);

            assertThat(result).isEqualTo(TenantSecretCompensationResult.RESTORED);
            assertThat(server.getRequestCount()).isEqualTo(4);
            server.takeRequest(2, TimeUnit.SECONDS);
            server.takeRequest(2, TimeUnit.SECONDS);
            server.takeRequest(2, TimeUnit.SECONDS);
            RecordedRequest compensationPut = server.takeRequest(2, TimeUnit.SECONDS);
            JsonNode body = objectMapper.readTree(compensationPut.getBody().readUtf8());
            assertThat(body.path("metadata").path("resourceVersion").asText()).isEqualTo("31");
            assertThat(domains(decodeTenants(body))).containsExactly("alpha");
        }
    }

    @Test
    void compensationDoesNotOverwriteNewerConcurrentChange() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");
        TenantConfig gamma = tenant("Гамма", "gamma", "секрет-c");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("40", List.of(alpha)));
            server.enqueue(secretResponse("41", List.of(alpha, beta)));
            server.enqueue(secretResponse("42", List.of(alpha, beta, gamma)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);
            TenantSecretCompensationResult result = updater.compensate(receipt);

            assertThat(result).isEqualTo(TenantSecretCompensationResult.SKIPPED_CONCURRENT_CHANGE);
            assertThat(server.getRequestCount()).isEqualTo(3);
        }
    }

    @Test
    void compensationDoesNotRetryAfterPutLosesResourceVersionRace() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("50", List.of(alpha)));
            server.enqueue(secretResponse("51", List.of(alpha, beta)));
            server.enqueue(secretResponse("51", List.of(alpha, beta)));
            server.enqueue(new MockResponse().setResponseCode(409));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);
            TenantSecretCompensationResult result = updater.compensate(receipt);

            assertThat(result).isEqualTo(TenantSecretCompensationResult.SKIPPED_CONCURRENT_CHANGE);
            assertThat(server.getRequestCount()).isEqualTo(4);
        }
    }

    @Test
    void compensationDoesNotTrustSuccessfulResponseWithDifferentTenantContent() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        TenantConfig alpha = tenant("Альфа", "alpha", "секрет-a");
        TenantConfig beta = tenant("Бета", "beta", "секрет-b");
        TenantConfig gamma = tenant("Гамма", "gamma", "секрет-c");

        try (MockWebServer server = httpsServer(serverCertificate(ca, "localhost"))) {
            server.enqueue(secretResponse("60", List.of(alpha)));
            server.enqueue(secretResponse("61", List.of(alpha, beta)));
            server.enqueue(secretResponse("61", List.of(alpha, beta)));
            server.enqueue(secretResponse("62", List.of(alpha, gamma)));
            server.enqueue(secretResponse("62", List.of(alpha, gamma)));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 3, Duration.ZERO);

            TenantSecretUpdateReceipt receipt = updater.upsertTenant(beta);
            TenantSecretCompensationResult result = updater.compensate(receipt);

            assertThat(result).isEqualTo(TenantSecretCompensationResult.SKIPPED_CONCURRENT_CHANGE);
            assertThat(server.getRequestCount()).isEqualTo(5);
        }
    }

    @Test
    void localModeIsNoopAndDoesNotRequireKubernetesCa() {
        Path missingToken = tempDirectory.resolve("missing-token");
        KubernetesTenantSecretUpdater updater = new KubernetesTenantSecretUpdater(
                missingToken,
                tempDirectory.resolve("missing-namespace"),
                tempDirectory.resolve("missing-ca"),
                "https://localhost:1",
                "tenants-secret",
                objectMapper
        );

        TenantSecretUpdateReceipt receipt = updater.upsertTenant(tenant("Локальный", "local", "секрет"));

        assertThat(receipt.persisted()).isFalse();
        assertThat(receipt.changed()).isFalse();
        assertThat(updater.compensate(receipt))
                .isEqualTo(TenantSecretCompensationResult.NOT_REQUIRED);
    }

    @Test
    void kubernetesModeFailsClosedWhenServiceAccountTokenIsMissing() {
        KubernetesTenantSecretUpdater updater = new KubernetesTenantSecretUpdater(
                tempDirectory.resolve("missing-kubernetes-token"),
                tempDirectory.resolve("missing-kubernetes-namespace"),
                tempDirectory.resolve("missing-kubernetes-ca"),
                "https://localhost:1",
                "tenants-secret",
                objectMapper,
                1,
                Duration.ZERO,
                true
        );

        assertThatThrownBy(() -> updater.upsertTenant(tenant("Тест", "test", "секрет")))
                .isInstanceOf(TenantConfigPersistenceException.class)
                .hasMessageContaining("ServiceAccount");
    }

    @Test
    void rejectsServerSignedByDifferentCa() throws Exception {
        HeldCertificate trustedCa = certificateAuthority("Доверенный тестовый CA");
        HeldCertificate untrustedCa = certificateAuthority("Недоверенный тестовый CA");
        HeldCertificate serverCertificate = serverCertificate(untrustedCa, "localhost");

        try (MockWebServer server = httpsServer(serverCertificate)) {
            server.enqueue(secretResponse("1", List.of()));
            KubernetesTenantSecretUpdater updater = updater(testFiles(trustedCa), server, 1, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(tenant("Тест", "test", "секрет")))
                    .isInstanceOf(TenantConfigPersistenceException.class);
        }
    }

    @Test
    void rejectsCertificateForDifferentHostname() throws Exception {
        HeldCertificate ca = certificateAuthority("Доверенный тестовый CA");
        HeldCertificate serverCertificate = serverCertificate(ca, "not-localhost.invalid");

        try (MockWebServer server = httpsServer(serverCertificate)) {
            server.enqueue(secretResponse("1", List.of()));
            KubernetesTenantSecretUpdater updater = updater(testFiles(ca), server, 1, Duration.ZERO);

            assertThatThrownBy(() -> updater.upsertTenant(tenant("Тест", "test", "секрет")))
                    .isInstanceOf(TenantConfigPersistenceException.class);
        }
    }

    @Test
    void failsClosedWhenServiceAccountCaIsMissing() throws Exception {
        TestFiles files = testFiles(certificateAuthority("Доверенный тестовый CA"));
        Files.delete(files.caPath());
        KubernetesTenantSecretUpdater updater = new KubernetesTenantSecretUpdater(
                files.tokenPath(),
                files.namespacePath(),
                files.caPath(),
                "https://localhost:1",
                "tenants-secret",
                objectMapper,
                1,
                Duration.ZERO
        );

        assertThatThrownBy(() -> updater.upsertTenant(tenant("Тест", "test", "секрет")))
                .isInstanceOf(TenantConfigPersistenceException.class)
                .hasMessageContaining("защищённое соединение");
    }

    private KubernetesTenantSecretUpdater updater(TestFiles files, MockWebServer server) {
        return updater(files, server, 3, Duration.ofMillis(5));
    }

    private KubernetesTenantSecretUpdater updater(TestFiles files,
                                                   MockWebServer server,
                                                   int maxAttempts,
                                                   Duration retryDelay) {
        return new KubernetesTenantSecretUpdater(
                files.tokenPath(),
                files.namespacePath(),
                files.caPath(),
                // MockWebServer на Windows может вернуть canonical loopback hostname
                // kubernetes.docker.internal. Фиксируем имя из SAN тестового сертификата,
                // сохраняя реальную проверку hostname в клиенте.
                "https://localhost:" + server.getPort(),
                "tenants-secret",
                objectMapper,
                maxAttempts,
                retryDelay
        );
    }

    private TestFiles testFiles(HeldCertificate ca) throws Exception {
        Path tokenPath = tempDirectory.resolve(UUID.randomUUID() + "-token");
        Path namespacePath = tempDirectory.resolve(UUID.randomUUID() + "-namespace");
        Path caPath = tempDirectory.resolve(UUID.randomUUID() + "-ca.crt");
        Files.writeString(tokenPath, "test-service-account-token");
        Files.writeString(namespacePath, "magistr");
        Files.writeString(caPath, ca.certificatePem());
        return new TestFiles(tokenPath, namespacePath, caPath);
    }

    private TenantConfig tenant(String name, String domain, String password) {
        return new TenantConfig(
                name,
                domain,
                "jdbc:postgresql://db/" + domain,
                "user-" + domain,
                password
        );
    }

    private MockResponse secretResponse(String resourceVersion, List<TenantConfig> tenants) {
        return new MockResponse()
                .setResponseCode(200)
                .setHeader("Content-Type", "application/json")
                .setBody(secretJson(resourceVersion, tenants));
    }

    private String secretJson(String resourceVersion, List<TenantConfig> tenants) {
        try {
            ObjectNode secret = objectMapper.createObjectNode();
            secret.put("apiVersion", "v1");
            secret.put("kind", "Secret");
            ObjectNode metadata = secret.putObject("metadata");
            metadata.put("name", "tenants-secret");
            metadata.put("namespace", "magistr");
            metadata.put("resourceVersion", resourceVersion);
            ObjectNode data = secret.putObject("data");
            data.put("tenants.json", encodeTenants(tenants));
            data.put("other.key", "cHJlc2VydmU=");
            return objectMapper.writeValueAsString(secret);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private String encodeTenants(List<TenantConfig> tenants) throws Exception {
        return Base64.getEncoder().encodeToString(
                objectMapper.writeValueAsBytes(tenants)
        );
    }

    private List<TenantConfig> decodeTenants(JsonNode secret) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(
                secret.path("data").path("tenants.json").asText()
        );
        return objectMapper.readValue(decoded, TENANT_LIST_TYPE);
    }

    private List<String> domains(List<TenantConfig> tenants) {
        return tenants.stream().map(TenantConfig::getDomain).sorted().toList();
    }

    private HeldCertificate certificateAuthority(String commonName) {
        return new HeldCertificate.Builder()
                .certificateAuthority(1)
                .commonName(commonName)
                .duration(1, TimeUnit.DAYS)
                .build();
    }

    private HeldCertificate serverCertificate(HeldCertificate ca, String hostname) {
        return new HeldCertificate.Builder()
                .commonName(hostname)
                .addSubjectAlternativeName(hostname)
                .duration(1, TimeUnit.DAYS)
                .signedBy(ca)
                .build();
    }

    private MockWebServer httpsServer(HeldCertificate serverCertificate) throws Exception {
        HandshakeCertificates certificates = new HandshakeCertificates.Builder()
                .heldCertificate(serverCertificate)
                .build();
        MockWebServer server = new MockWebServer();
        server.useHttps(certificates.sslSocketFactory(), false);
        server.start();
        return server;
    }

    private final class CasDispatcher extends Dispatcher {
        private final Object monitor = new Object();
        private final CountDownLatch firstReads;
        private final AtomicInteger initialReadCount = new AtomicInteger();
        private final AtomicInteger conflicts = new AtomicInteger();
        private List<TenantConfig> currentTenants;
        private int resourceVersion = 1;

        private CasDispatcher(List<TenantConfig> initialTenants, boolean synchronizeFirstReads) {
            this.currentTenants = new ArrayList<>(initialTenants);
            this.firstReads = synchronizeFirstReads ? new CountDownLatch(2) : null;
        }

        @Override
        public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
            if ("GET".equals(request.getMethod())) {
                MockResponse snapshot;
                synchronized (monitor) {
                    snapshot = secretResponse(String.valueOf(resourceVersion), currentTenants);
                }
                if (firstReads != null && initialReadCount.incrementAndGet() <= 2) {
                    firstReads.countDown();
                    if (!firstReads.await(5, TimeUnit.SECONDS)) {
                        return new MockResponse().setResponseCode(500);
                    }
                }
                return snapshot;
            }

            if ("PUT".equals(request.getMethod())) {
                try {
                    JsonNode body = objectMapper.readTree(request.getBody().readUtf8());
                    int expectedVersion = body.path("metadata").path("resourceVersion").asInt(-1);
                    List<TenantConfig> requestedTenants = decodeTenants(body);
                    synchronized (monitor) {
                        if (expectedVersion != resourceVersion) {
                            conflicts.incrementAndGet();
                            return new MockResponse().setResponseCode(409);
                        }
                        currentTenants = new ArrayList<>(requestedTenants);
                        resourceVersion++;
                        return secretResponse(String.valueOf(resourceVersion), currentTenants);
                    }
                } catch (Exception e) {
                    return new MockResponse().setResponseCode(400);
                }
            }

            return new MockResponse().setResponseCode(405);
        }

        private List<TenantConfig> currentTenants() {
            synchronized (monitor) {
                return new ArrayList<>(currentTenants);
            }
        }

        private int conflictCount() {
            return conflicts.get();
        }
    }

    private record TestFiles(Path tokenPath, Path namespacePath, Path caPath) {
    }
}
