package ch.zhaw.gartenverwaltung.io;

import ch.zhaw.gartenverwaltung.models.GardenSchedule;
import ch.zhaw.gartenverwaltung.models.PlantNotFoundException;
import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Task;
import javafx.application.Platform;
import org.junit.jupiter.api.*;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class JsonTaskListTest {

    TaskList testDatabase;
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private final URL dbDataSource = this.getClass().getResource("test-taskdb.json");
    private final URL testFile = this.getClass().getResource("template-taskdb.json");


    @BeforeAll
    static void setUpAll() {
        try{
            Platform.startup(()->{});
        }catch (IllegalStateException ise){
            //ignore double launches

        }
    }

    @AfterAll
    static void tearDownAll() {
        //Dont do: Platform.exit();
    }

    @BeforeEach
    void connectToDb() throws URISyntaxException, IOException {
        assertNotNull(testFile);
        assertNotNull(dbDataSource);
        Files.copy(Path.of(testFile.toURI()), Path.of(dbDataSource.toURI()), StandardCopyOption.REPLACE_EXISTING);
        testDatabase = new JsonTaskList(dbDataSource);
    }

    private void reloadDb() {
        testDatabase = new JsonTaskList(dbDataSource);
    }

    @Test
    @DisplayName("Check if results are retrieved completely")
    void getTasks() {

        List<Task> taskList;
        try {
            taskList = testDatabase.getTaskList(LocalDate.parse("2022-04-30", formatter),
                    LocalDate.parse("2022-05-31", formatter));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
        Assertions.assertEquals(Arrays.asList(1L, 2L, 5L), ids);

    }

    @Test
    @DisplayName("Add task.")
    void addTask() {
        Task task = new Task("Testtask", "This is a test Task.", LocalDate.parse("2022-05-01", formatter), 1);
        try {
            testDatabase.saveTask(task);
            List<Task> taskList;
            try {
                taskList = testDatabase.getTaskList(LocalDate.parse("2022-04-30", formatter),
                        LocalDate.parse("2022-05-31", formatter));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
            Assertions.assertEquals(Arrays.asList(1L, 2L, 5L, 9L), ids);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    @DisplayName("Remove task.")
    void removeTask() {
        Task task = new Task("Dummy", "Dummy", LocalDate.parse("2022-05-31", formatter), 1).withId(2);
        try {
            testDatabase.removeTask(task);
            List<Task> taskList;

            taskList = testDatabase.getTaskList(LocalDate.parse("2022-04-30", formatter),
                    LocalDate.parse("2022-05-31", formatter));

            List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
            Assertions.assertEquals(Arrays.asList(1L, 5L), ids);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


    @Test
    void getTaskForCrop() {
        List<Task> taskList;
        try {
            taskList = testDatabase.getTaskForCrop(0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<Long> ids = taskList.stream().map(t -> t.getId().orElse(0L)).toList();
        Assertions.assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), ids);
    }


    @Test
    void removeTasksForCrop() {
        List<Task> taskList;
        try {
            testDatabase.removeTasksForCrop(0);
            taskList = testDatabase.getTaskForCrop(0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Assertions.assertEquals(0, taskList.size());

        try {
            taskList = testDatabase.getTaskForCrop(1);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<Long> ids = (taskList.stream().map( task -> task.getId().orElse(0L)).toList());
        Assertions.assertEquals(Arrays.asList(7L, 8L), ids);

    }

    @Test
    void testDefaultConstructor() {
        JsonTaskList db = new JsonTaskList();
        try {
            assertNotNull(db.getTaskForCrop(0));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Test
    void testSubscription() throws IOException {
        TaskList.TaskListObserver mockObs = Mockito.mock(TaskList.TaskListObserver.class);
        testDatabase.subscribe(mockObs);
        try {
            testDatabase.removeTasksForCrop(0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        ArgumentCaptor<List<Task>> captor = ArgumentCaptor.forClass(List.class);

        verify(mockObs, times(1)).onChange(captor.capture());
        List<Long> ids = captor.getValue().stream().map(t -> t.getId().orElse(0L)).toList();
        Assertions.assertEquals(Arrays.asList(7L, 8L), ids);
    }

    @Test
    @Tag("IntegrationTest")
    void testComplete() {
        JsonPlantList plantList = new JsonPlantList();
        try {
            testDatabase.removeTasksForCrop(0);
            testDatabase.removeTasksForCrop(1);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        GardenSchedule gardenSchedule = null;
        try {
            gardenSchedule = new GardenSchedule(testDatabase, plantList);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        Crop crop = new Crop(3L, LocalDate.parse("2022-12-01", formatter));

        try {
            gardenSchedule.planTasksForCrop(crop);

            reloadDb();

            List<Task> tasks = gardenSchedule.getTaskList();
            List<String> tasknames = (tasks.stream().map( task -> task.getName().substring(0,3).toLowerCase()).toList());
            Assertions.assertEquals(Arrays.asList("ger","wat","hil","har"), tasknames);
        } catch (IOException | PlantNotFoundException | HardinessZoneNotSetException e) {
            throw new RuntimeException(e);
        }

    }
}