package de.gecheckt.pdf.umbenenner.adapter.in.gui;

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

import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiApiKeyMerger;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationFileSnapshot;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationValues;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiProviderApiKeyState;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiProviderConfigurationState;
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;

/**
 * Unit tests for the save-related model logic.
 * <p>
 * These tests exercise the API-key preservation merge via {@link GuiApiKeyMerger} and the
 * {@link GuiConfigurationSaveResult} without requiring a JavaFX runtime.
 */
class GuiConfigurationEditorWorkspaceSaveTest {

    // =========================================================================
    // API-key preservation via GuiApiKeyMerger
    // =========================================================================

    @Test
    void merge_preservesBaselineKeyWhenEditorFieldIsEmpty() {
        // Baseline: Claude has a non-empty API key.
        GuiConfigurationValues baseline = buildValues("sk-baseline-claude", "sk-openai");
        // Current: user cleared the Claude API key in the editor.
        GuiConfigurationValues current = buildValues("", "sk-openai");

        GuiConfigurationEditorState state = buildState(baseline, current);
        GuiApiKeyMerger.MergeResult result = GuiApiKeyMerger.merge(state);

        String claudeKey = result.values().providerConfiguration(AiProviderFamily.CLAUDE)
                .apiKey().propertyValue();
        assertEquals("sk-baseline-claude", claudeKey,
                "Baseline API key must be preserved when the editor field is empty");
        assertTrue(result.hasPreservationNote(), "Preservation note must be set");
        assertEquals("claude", result.preservedProviderIdentifier());
    }

    @Test
    void merge_doesNotPreserveKeyWhenEditorFieldIsNotEmpty() {
        GuiConfigurationValues baseline = buildValues("sk-old", "sk-openai");
        GuiConfigurationValues current = buildValues("sk-new", "sk-openai");

        GuiConfigurationEditorState state = buildState(baseline, current);
        GuiApiKeyMerger.MergeResult result = GuiApiKeyMerger.merge(state);

        String claudeKey = result.values().providerConfiguration(AiProviderFamily.CLAUDE)
                .apiKey().propertyValue();
        assertEquals("sk-new", claudeKey,
                "Non-empty editor API key must not be replaced by the baseline");
        assertFalse(result.hasPreservationNote(), "No preservation note when field is not empty");
    }

    @Test
    void merge_preservesNothingWhenBaselineKeyIsAlsoEmpty() {
        GuiConfigurationValues baseline = buildValues("", "");
        GuiConfigurationValues current = buildValues("", "");

        GuiConfigurationEditorState state = buildState(baseline, current);
        GuiApiKeyMerger.MergeResult result = GuiApiKeyMerger.merge(state);

        String claudeKey = result.values().providerConfiguration(AiProviderFamily.CLAUDE)
                .apiKey().propertyValue();
        assertEquals("", claudeKey,
                "Empty baseline key must not trigger preservation");
        assertFalse(result.hasPreservationNote());
    }

    @Test
    void merge_preservesBothProviderKeysIndependently() {
        GuiConfigurationValues baseline = buildValues("sk-claude-base", "sk-openai-base");
        // User cleared both keys.
        GuiConfigurationValues current = buildValues("", "");

        GuiConfigurationEditorState state = buildState(baseline, current);
        GuiApiKeyMerger.MergeResult result = GuiApiKeyMerger.merge(state);

        assertEquals("sk-claude-base",
                result.values().providerConfiguration(AiProviderFamily.CLAUDE).apiKey().propertyValue(),
                "Claude key must be preserved");
        assertEquals("sk-openai-base",
                result.values().providerConfiguration(AiProviderFamily.OPENAI_COMPATIBLE).apiKey().propertyValue(),
                "OpenAI key must be preserved");
        // Only the first provider is recorded in preservedProviderIdentifier.
        assertTrue(result.hasPreservationNote());
    }

    @Test
    void merge_preservesOnlyProviderWithEmptyField() {
        GuiConfigurationValues baseline = buildValues("sk-claude-base", "sk-openai-base");
        // User cleared only the OpenAI key, kept the Claude key.
        GuiConfigurationValues current = buildValues("sk-claude-new", "");

        GuiConfigurationEditorState state = buildState(baseline, current);
        GuiApiKeyMerger.MergeResult result = GuiApiKeyMerger.merge(state);

        assertEquals("sk-claude-new",
                result.values().providerConfiguration(AiProviderFamily.CLAUDE).apiKey().propertyValue(),
                "Claude key must be the edited value");
        assertEquals("sk-openai-base",
                result.values().providerConfiguration(AiProviderFamily.OPENAI_COMPATIBLE).apiKey().propertyValue(),
                "OpenAI key must be preserved from baseline");
        assertTrue(result.hasPreservationNote());
        assertEquals("openai-compatible", result.preservedProviderIdentifier(),
                "Preserved provider must be the one whose field was cleared");
    }

    // =========================================================================
    // GuiConfigurationSaveResult
    // =========================================================================

    @Test
    void saveResult_withoutPreservation_hasNoNote() {
        Path path = Path.of("config/application.properties");
        GuiConfigurationSaveResult result = GuiConfigurationSaveResult.saved(path);

        assertEquals(path, result.savedPath());
        assertFalse(result.hasApiKeyPreservationNote());
    }

    @Test
    void saveResult_withPreservation_carriesProviderIdentifier() {
        Path path = Path.of("config/application.properties");
        GuiConfigurationSaveResult result = GuiConfigurationSaveResult.savedWithPreservedKey(path, "claude");

        assertEquals(path, result.savedPath());
        assertTrue(result.hasApiKeyPreservationNote());
        assertEquals("claude", result.apiKeyPreservedForProvider());
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    private GuiConfigurationValues buildValues(String claudeApiKey, String openaiApiKey) {
        Map<AiProviderFamily, GuiProviderConfigurationState> providers = new LinkedHashMap<>();
        providers.put(AiProviderFamily.CLAUDE, new GuiProviderConfigurationState(
                "https://api.anthropic.com", "claude-model", "60",
                GuiProviderApiKeyState.unresolved(claudeApiKey)));
        providers.put(AiProviderFamily.OPENAI_COMPATIBLE, new GuiProviderConfigurationState(
                "https://api.openai.com/v1", "gpt-4o-mini", "30",
                GuiProviderApiKeyState.unresolved(openaiApiKey)));
        return new GuiConfigurationValues(
                "./source", "./target", "./db.sqlite", "./prompt.txt",
                "./app.lock", "./logs", "INFO", "3", "10", "1000",
                "60", "false", "claude", providers);
    }

    private GuiConfigurationEditorState buildState(GuiConfigurationValues baseline,
                                                   GuiConfigurationValues current) {
        Properties props = new Properties();
        GuiConfigurationFileSnapshot snapshot = new GuiConfigurationFileSnapshot(
                Path.of("config/application.properties"), props);
        return new GuiConfigurationEditorState(Optional.of(snapshot), baseline, current, Optional.empty());
    }
}
