package org.jabref.gui.entryeditor;

import java.util.List;

import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.keyboard.KeyBindingRepository;
import org.jabref.gui.search.SearchType;
import org.jabref.gui.undo.CountingUndoManager;
import org.jabref.logic.bibtex.FieldPreferences;
import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.util.OptionalObjectProperty;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.field.UnknownField;
import org.jabref.model.util.DummyFileUpdateMonitor;

import io.github.kusoroadeolu.veneer.BibTeXSyntaxHighlighter;
import org.fxmisc.richtext.CodeArea;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.testfx.api.FxRobot;
import org.testfx.framework.junit5.ApplicationExtension;
import org.testfx.framework.junit5.Start;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(ApplicationExtension.class)
class SourceTabTest {

    private Stage stage;
    private Scene scene;
    private CodeArea area;
    private TabPane pane;
    private SourceTab sourceTab;

    @Start
    public void onStart(Stage stage) {
        area = new CodeArea();
        area.appendText("some example\n text to go here\n across a couple of \n lines....");
        StateManager stateManager = mock(StateManager.class);
        when(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH)).thenReturn(OptionalObjectProperty.empty());
        when(stateManager.searchQueryProperty()).thenReturn(mock(StringProperty.class));
        when(stateManager.activeTabProperty()).thenReturn(OptionalObjectProperty.empty());
        KeyBindingRepository keyBindingRepository = new KeyBindingRepository(List.of(), List.of());
        ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS);
        when(importFormatPreferences.bibEntryPreferences().getKeywordSeparator()).thenReturn(',');
        FieldPreferences fieldPreferences = mock(FieldPreferences.class);
        when(fieldPreferences.getNonWrappableFields()).thenReturn(FXCollections.emptyObservableList());

        sourceTab = new SourceTab(
                new CountingUndoManager(),
                fieldPreferences,
                importFormatPreferences,
                new DummyFileUpdateMonitor(),
                mock(DialogService.class),
                mock(BibEntryTypesManager.class),
                keyBindingRepository,
                stateManager,
                new BibTeXSyntaxHighlighter()
        );
        pane = new TabPane(
                new Tab("main area", area),
                new Tab("other tab", new Label("some text")),
                sourceTab
        );
        scene = new Scene(pane);
        this.stage = stage;

        stage.setScene(scene);
        stage.setWidth(400);
        stage.setHeight(400);
        stage.show();

        // select the area's tab
        pane.getSelectionModel().select(0);
    }

    @Test
    void switchingFromSourceTabDoesNotThrowException(FxRobot robot) {
        BibEntry entry = new BibEntry();
        entry.setField(new UnknownField("test"), "testvalue");

        // Update source editor. In production currentEntry is bound to the view model; here we drive the
        // property directly, since notifyAboutFocus no longer sets it.
        robot.interact(() -> pane.getSelectionModel().select(2));
        robot.interact(() -> {
            sourceTab.currentEntryProperty().set(entry);
            sourceTab.notifyAboutFocus(entry);
        });
        robot.clickOn(1200, 500);
        robot.interrupt(100);

        // Switch to different tab & update entry
        robot.interact(() -> pane.getSelectionModel().select(1));
        robot.interact(() -> stage.setWidth(600));
        robot.interact(() -> entry.setField(new UnknownField("test"), "new value"));

        // No exception should be thrown
        robot.interrupt(100);
    }

    @Test
    void updatingPreviouslyBoundEntryDoesNotResetCurrentSource(FxRobot robot) {
        BibEntry firstEntry = new BibEntry().withField(new UnknownField("title"), "First entry");
        BibEntry secondEntry = new BibEntry().withField(new UnknownField("title"), "Second entry");

        robot.interact(() -> {
            pane.getSelectionModel().select(sourceTab);
            sourceTab.currentEntryProperty().set(firstEntry);
            sourceTab.notifyAboutFocus(firstEntry);

            sourceTab.currentEntryProperty().set(secondEntry);
            sourceTab.notifyAboutFocus(secondEntry);

            jfx.incubator.scene.control.richtext.CodeArea sourceArea =
                    (jfx.incubator.scene.control.richtext.CodeArea) sourceTab.getContent();
            sourceArea.clear();
            sourceArea.appendText("Unsaved source for the second entry");

            firstEntry.setField(new UnknownField("author"), "Author");
            assertEquals("Unsaved source for the second entry", sourceArea.getText());
        });
    }
}
