package org.jabref.logic.bibtex.comparator;

import java.util.List;
import java.util.Optional;

import org.jabref.model.database.BibDatabase;
import org.jabref.model.entry.BibtexString;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

class BibStringDiffTest {

    private final BibDatabase originalDataBase = mock(BibDatabase.class);
    private final BibDatabase newDataBase = mock(BibDatabase.class);
    private final BibStringDiff diff = new BibStringDiff(new BibtexString("name2", "content2"), new BibtexString("name2", "content3"));

    @BeforeEach
    void setUp() {
        when(originalDataBase.hasNoStrings()).thenReturn(false);
        when(newDataBase.hasNoStrings()).thenReturn(false);
    }

    @Test
    void compareTest() {
        when(originalDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content"), new BibtexString("name2", "content2")));
        when(newDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content"), new BibtexString("name2", "content3")));

        List<BibStringDiff> result = BibStringDiff.compare(originalDataBase, newDataBase);
        assertEquals(List.of(diff), result);
    }

    @Test
    void equalTest() {
        BibStringDiff other = new BibStringDiff(diff.getOriginalString().orElse(null), diff.getNewString().orElse(null));
        assertEquals(diff, other);
        assertEquals(diff.hashCode(), other.hashCode());
    }

    @Test
    void notEqualTest() {
        BibStringDiff other = new BibStringDiff(diff.getNewString().orElse(null), diff.getOriginalString().orElse(null));
        assertNotEquals(diff, other);
        assertNotEquals(diff.hashCode(), other.hashCode());
    }

    @Test
    void identicalObjectsAreEqual() {
        assertEquals(diff, diff);
    }

    @Test
    void compareToNullObjectIsFalse() {
        assertNotEquals(null, diff);
    }

    @Test
    void compareToDifferentClassIsFalse() {
        assertNotEquals(new Object(), diff);
    }

    @Test
    void getters() {
        BibtexString bsOne = new BibtexString("aKahle", "Kahle, Brewster");
        BibtexString bsTwo = new BibtexString("iMIT", "Institute of Technology");
        BibStringDiff diff = new BibStringDiff(bsOne, bsTwo);
        assertEquals(diff.getOriginalString(), Optional.of(bsOne));
        assertEquals(diff.getNewString(), Optional.of(bsTwo));
    }

    @Test
    void compareEmptyDatabases() {
        when(originalDataBase.hasNoStrings()).thenReturn(true);
        when(newDataBase.hasNoStrings()).thenReturn(true);

        assertEquals(List.of(), BibStringDiff.compare(originalDataBase, newDataBase));
    }

    @Test
    void compareNameChange() {
        when(originalDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content")));
        when(newDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name2", "content")));

        List<BibStringDiff> result = BibStringDiff.compare(originalDataBase, newDataBase);
        BibStringDiff expectedDiff = new BibStringDiff(new BibtexString("name", "content"), new BibtexString("name2", "content"));
        assertEquals(List.of(expectedDiff), result);
    }

    @Test
    void compareNoDiff() {
        when(originalDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content")));
        when(newDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content")));

        List<BibStringDiff> result = BibStringDiff.compare(originalDataBase, newDataBase);
        assertEquals(List.of(), result);
    }

    @Test
    void compareRemovedString() {
        when(originalDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content")));
        when(newDataBase.getStringValues()).thenReturn(List.of());

        List<BibStringDiff> result = BibStringDiff.compare(originalDataBase, newDataBase);
        BibStringDiff expectedDiff = new BibStringDiff(new BibtexString("name", "content"), null);
        assertEquals(List.of(expectedDiff), result);
    }

    @Test
    void compareAddString() {
        when(originalDataBase.getStringValues()).thenReturn(List.of());
        when(newDataBase.getStringValues()).thenReturn(List.of(new BibtexString("name", "content")));

        List<BibStringDiff> result = BibStringDiff.compare(originalDataBase, newDataBase);
        BibStringDiff expectedDiff = new BibStringDiff(null, new BibtexString("name", "content"));
        assertEquals(List.of(expectedDiff), result);
    }
}
