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

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

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

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiChangeState;
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.GuiConfigurationTemplateFactory;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationValues;

/**
 * Unit tests for dirty-state derivation on {@link GuiConfigurationEditorState}.
 * <p>
 * These tests exercise the comparison between baseline and current values, the initial
 * clean state of the standard template, and the reset after a simulated save.
 */
class GuiDirtyStateTest {

    // =========================================================================
    // Standard template is always clean after creation
    // =========================================================================

    @Test
    void standardTemplate_isCleanAfterCreation() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();

        assertFalse(state.isDirty(), "Freshly created standard template must not be dirty");
        assertFalse(state.changeState().isDirty(), "changeState() must agree with isDirty()");
    }

    @Test
    void blankStartState_isCleanAfterCreation() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createBlankStartState();

        assertFalse(state.isDirty(), "Blank start state must not be dirty");
        assertFalse(state.changeState().isDirty());
    }

    // =========================================================================
    // Any change to values makes the state dirty
    // =========================================================================

    @Test
    void changingValues_makesStateDirty() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationEditorState dirty = state.withValues(differentValues(state));

        assertTrue(dirty.isDirty(), "State with different values must be dirty");
        assertTrue(dirty.changeState() == GuiChangeState.DIRTY);
    }

    @Test
    void revertingValues_makesStateClean() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationEditorState dirty = state.withValues(differentValues(state));
        GuiConfigurationEditorState reverted = dirty.withValues(state.baselineValues());

        assertFalse(reverted.isDirty(), "Reverting to baseline values must restore a clean state");
    }

    // =========================================================================
    // New configuration: clean until values differ from template baseline
    // =========================================================================

    @Test
    void newConfiguration_isClean_whenValuesMatchTemplate() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();

        assertTrue(state.isNewConfiguration(), "Precondition: no file snapshot");
        assertFalse(state.isDirty(), "New configuration matching the template baseline must be clean");
    }

    @Test
    void newConfiguration_isDirty_whenValuesDifferFromTemplate() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationEditorState dirty = state.withValues(differentValues(state));

        assertTrue(dirty.isNewConfiguration(), "Precondition: still no file snapshot");
        assertTrue(dirty.isDirty(), "New configuration with changed values must be dirty");
    }

    // =========================================================================
    // After save: baseline is advanced to saved values, state becomes clean
    // =========================================================================

    @Test
    void afterSave_stateBecomesClean() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationEditorState dirty = state.withValues(differentValues(state));

        assertTrue(dirty.isDirty(), "Precondition: dirty before save");

        // Simulate what the workspace does after a successful write:
        // baseline = values, new snapshot attached.
        GuiConfigurationFileSnapshot snapshot = new GuiConfigurationFileSnapshot(
                Path.of("config/application.properties"), new Properties());
        GuiConfigurationEditorState afterSave = new GuiConfigurationEditorState(
                Optional.of(snapshot),
                dirty.values(),       // baseline = saved values
                dirty.values(),       // current = same saved values
                Optional.empty());

        assertFalse(afterSave.isDirty(), "After save baseline=values, state must be clean");
    }

    @Test
    void markClean_advancesBaselineToCurrentValues() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationValues changed = differentValues(state);
        GuiConfigurationEditorState dirty = state.withValues(changed);

        assertTrue(dirty.isDirty(), "Precondition: must be dirty");

        GuiConfigurationEditorState clean = dirty.markClean();

        assertFalse(clean.isDirty(), "markClean() must yield a clean state");
        // markClean resets values to baseline (not the other way around).
        // The actual implementation resets current values to the baseline.
        assertFalse(clean.isDirty());
    }

    // =========================================================================
    // Loaded-file state: clean when values match the baseline read from disk
    // =========================================================================

    @Test
    void loadedState_isCleanWhenValuesMatchBaseline() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationFileSnapshot snapshot = new GuiConfigurationFileSnapshot(
                Path.of("config/application.properties"), new Properties());
        // Simulate a loaded state: baseline = current = template values.
        GuiConfigurationEditorState loaded = new GuiConfigurationEditorState(
                Optional.of(snapshot), state.values(), state.values(), Optional.empty());

        assertFalse(loaded.isDirty(), "Loaded state with matching baseline must be clean");
        assertFalse(loaded.isNewConfiguration());
    }

    @Test
    void loadedState_isDirtyAfterEdit() {
        GuiConfigurationEditorState state = GuiConfigurationTemplateFactory.createStandardTemplate();
        GuiConfigurationFileSnapshot snapshot = new GuiConfigurationFileSnapshot(
                Path.of("config/application.properties"), new Properties());
        GuiConfigurationEditorState loaded = new GuiConfigurationEditorState(
                Optional.of(snapshot), state.values(), state.values(), Optional.empty());

        GuiConfigurationEditorState dirty = loaded.withValues(differentValues(loaded));

        assertTrue(dirty.isDirty(), "Editing the values of a loaded state must make it dirty");
    }

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

    private static GuiConfigurationValues differentValues(GuiConfigurationEditorState state) {
        GuiConfigurationValues v = state.values();
        // Change the source folder to produce different values.
        return new GuiConfigurationValues(
                v.sourceFolder() + "_changed",
                v.targetFolder(),
                v.sqliteFile(),
                v.promptTemplateFile(),
                v.runtimeLockFile(),
                v.logDirectory(),
                v.logLevel(),
                v.maxRetriesTransient(),
                v.maxPages(),
                v.maxTextCharacters(),
                v.maxTitleLength(),
                v.logAiSensitive(),
                v.activeProviderFamily(),
                v.providerConfigurations());
    }
}
