package de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog;

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

import java.time.Instant;
import java.util.List;

import org.junit.jupiter.api.Test;

/**
 * Tests for {@link ModelCatalogResult} and its permitted sub-types.
 */
class ModelCatalogResultTest {

    // ── Success ─────────────────────────────────────────────────────────────

    @Test
    void success_storesAllFields() {
        var now = Instant.now();
        var result = new ModelCatalogResult.Success("claude", List.of("claude-3-5-sonnet", "claude-3-opus"), now);

        assertThat(result.providerIdentifier()).isEqualTo("claude");
        assertThat(result.models()).containsExactly("claude-3-5-sonnet", "claude-3-opus");
        assertThat(result.loadedAt()).isEqualTo(now);
    }

    @Test
    void success_modelListIsDefensiveCopy() {
        var mutable = new java.util.ArrayList<>(List.of("model-a"));
        var result = new ModelCatalogResult.Success("claude", mutable, Instant.now());
        mutable.add("model-b");

        assertThat(result.models()).containsExactly("model-a");
    }

    @Test
    void success_rejectsNullProviderIdentifier() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.Success(null, List.of("m"), Instant.now()));
    }

    @Test
    void success_rejectsNullModelList() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.Success("claude", null, Instant.now()));
    }

    @Test
    void success_rejectsEmptyModelList() {
        assertThatIllegalArgumentException()
                .isThrownBy(() -> new ModelCatalogResult.Success("claude", List.of(), Instant.now()));
    }

    @Test
    void success_rejectsNullTimestamp() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.Success("claude", List.of("m"), null));
    }

    // ── EmptyList ────────────────────────────────────────────────────────────

    @Test
    void emptyList_storesAllFields() {
        var now = Instant.now();
        var result = new ModelCatalogResult.EmptyList("openai-compatible", now);

        assertThat(result.providerIdentifier()).isEqualTo("openai-compatible");
        assertThat(result.loadedAt()).isEqualTo(now);
    }

    @Test
    void emptyList_rejectsNullProviderIdentifier() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.EmptyList(null, Instant.now()));
    }

    @Test
    void emptyList_rejectsNullTimestamp() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.EmptyList("claude", null));
    }

    // ── IncompleteConfiguration ──────────────────────────────────────────────

    @Test
    void incompleteConfiguration_storesAllFields() {
        var result = new ModelCatalogResult.IncompleteConfiguration("claude", "API-Schluessel fehlt");

        assertThat(result.providerIdentifier()).isEqualTo("claude");
        assertThat(result.missingReason()).isEqualTo("API-Schluessel fehlt");
    }

    @Test
    void incompleteConfiguration_rejectsNullProviderIdentifier() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.IncompleteConfiguration(null, "reason"));
    }

    @Test
    void incompleteConfiguration_rejectsNullMissingReason() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.IncompleteConfiguration("claude", null));
    }

    // ── TechnicalFailure ─────────────────────────────────────────────────────

    @Test
    void technicalFailure_storesAllFields() {
        var result = new ModelCatalogResult.TechnicalFailure("openai-compatible", "HTTP_ERROR", "503 Service Unavailable");

        assertThat(result.providerIdentifier()).isEqualTo("openai-compatible");
        assertThat(result.errorCategory()).isEqualTo("HTTP_ERROR");
        assertThat(result.errorDetail()).isEqualTo("503 Service Unavailable");
    }

    @Test
    void technicalFailure_rejectsNullProviderIdentifier() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.TechnicalFailure(null, "cat", "detail"));
    }

    @Test
    void technicalFailure_rejectsNullErrorCategory() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.TechnicalFailure("claude", null, "detail"));
    }

    @Test
    void technicalFailure_rejectsNullErrorDetail() {
        assertThatNullPointerException()
                .isThrownBy(() -> new ModelCatalogResult.TechnicalFailure("claude", "cat", null));
    }

    // ── Pattern-matching exhaustiveness ──────────────────────────────────────

    @Test
    void patternMatchingSwitchCoversAllPermittedSubtypes() {
        ModelCatalogResult success = new ModelCatalogResult.Success("claude", List.of("m"), Instant.now());
        ModelCatalogResult empty = new ModelCatalogResult.EmptyList("claude", Instant.now());
        ModelCatalogResult incomplete = new ModelCatalogResult.IncompleteConfiguration("claude", "reason");
        ModelCatalogResult failure = new ModelCatalogResult.TechnicalFailure("claude", "TIMEOUT", "detail");

        assertThat(classifyResult(success)).isEqualTo("success");
        assertThat(classifyResult(empty)).isEqualTo("empty");
        assertThat(classifyResult(incomplete)).isEqualTo("incomplete");
        assertThat(classifyResult(failure)).isEqualTo("failure");
    }

    private String classifyResult(ModelCatalogResult result) {
        return switch (result) {
            case ModelCatalogResult.Success s -> "success";
            case ModelCatalogResult.EmptyList e -> "empty";
            case ModelCatalogResult.IncompleteConfiguration i -> "incomplete";
            case ModelCatalogResult.TechnicalFailure t -> "failure";
        };
    }
}
