package ch.zhaw.gartenverwaltung.models;

import ch.zhaw.gartenverwaltung.io.*;
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
import ch.zhaw.gartenverwaltung.types.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.time.LocalDate;
import java.time.MonthDay;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class GardenScheduleTest {
    LocalDate exampleStartDate = LocalDate.of(2020, 2, 2);
    TaskList taskList;
    PlantList plantList;
    List<Plant> examplePlantList;
    List<Task> exampleTaskList;
    List<TaskTemplate> exampleTaskTemplateList;
    GardenSchedule model;

    @BeforeEach
    void setUp() throws IOException, HardinessZoneNotSetException {
        createExampleTaskList();
        createExampleTaskTemplateList();
        createExamplePlantList();
        taskList = mockTaskDatabase(exampleTaskList);
        plantList = mockPlantDatabase(examplePlantList);
        model = new GardenSchedule(taskList, plantList);
    }

    PlantList mockPlantDatabase(List<Plant> plantList) throws HardinessZoneNotSetException, IOException {
        PlantList plantDatabase = mock(JsonPlantList.class);
        when(plantDatabase.getPlantList(HardinessZone.ZONE_8A)).thenReturn(plantList);
        when(plantDatabase.getPlantById(HardinessZone.ZONE_8A,20)).thenReturn(Optional.of(examplePlantList.get(0)));
        return plantDatabase;
    }

    private TaskList mockTaskDatabase(List<Task> exampleTaskList) throws IOException {
        TaskList taskList = mock(JsonTaskList.class);
        when(taskList.getTaskList(LocalDate.MIN, LocalDate.MAX)).thenReturn(exampleTaskList);
        when(taskList.getTaskList(LocalDate.now(), LocalDate.now().plusDays(7 - 1))).thenReturn(exampleTaskList);

        when(taskList.getTaskList(LocalDate.now(), LocalDate.MAX)).thenReturn((exampleTaskList.subList(1, 4)));

        List<Task> pastTasks = new ArrayList<>();
        pastTasks.add(exampleTaskList.get(0));
        pastTasks.add(exampleTaskList.get(2));
        pastTasks.add(exampleTaskList.get(4));
        when(taskList.getTaskList(LocalDate.MIN, LocalDate.now())).thenReturn(pastTasks);


        when(taskList.getTaskList(LocalDate.now(), LocalDate.now())).thenReturn(List.of(exampleTaskList.get(2)));
        when(taskList.getTaskList(LocalDate.now().plusDays(1L), LocalDate.now().plusDays(1L))).thenReturn(List.of(exampleTaskList.get(1)));
        when(taskList.getTaskList(LocalDate.now().plusDays(2L), LocalDate.now().plusDays(2L))).thenReturn(List.of());
        when(taskList.getTaskList(LocalDate.now().plusDays(3L), LocalDate.now().plusDays(3L))).thenReturn(List.of());
        when(taskList.getTaskList(LocalDate.now().plusDays(4L), LocalDate.now().plusDays(4L))).thenReturn(List.of());
        when(taskList.getTaskList(LocalDate.now().plusDays(5L), LocalDate.now().plusDays(5L))).thenReturn(List.of());
        when(taskList.getTaskList(LocalDate.now().plusDays(6L), LocalDate.now().plusDays(6L))).thenReturn(List.of());

        return taskList;
    }

    void createExampleTaskTemplateList(){
        exampleTaskTemplateList = new ArrayList<>();
        exampleTaskTemplateList.add(mock(TaskTemplate.class));
        exampleTaskTemplateList.add(mock(TaskTemplate.class));
        exampleTaskTemplateList.add(mock(TaskTemplate.class));
        exampleTaskTemplateList.add(mock(TaskTemplate.class));
        when(exampleTaskTemplateList.get(0).generateTask(exampleStartDate, 30)).thenReturn(exampleTaskList.get(0));
        when(exampleTaskTemplateList.get(1).generateTask(exampleStartDate, 30)).thenReturn(exampleTaskList.get(1));
        when(exampleTaskTemplateList.get(2).generateTask(exampleStartDate, 30)).thenReturn(exampleTaskList.get(2));
        when(exampleTaskTemplateList.get(3).generateTask(exampleStartDate, 30)).thenReturn(exampleTaskList.get(3));
    }

    void createExamplePlantList(){
        examplePlantList = new ArrayList<>();
        examplePlantList.add(new Plant(
                20,
                "summertime onion",
                "Onion, (Allium cepa), herbaceous biennial plant in the amaryllis family (Amaryllidaceae) grown for its edible bulb. The onion is likely native to southwestern Asia but is now grown throughout the world, chiefly in the temperate zones. Onions are low in nutrients but are valued for their flavour and are used widely in cooking. They add flavour to such dishes as stews, roasts, soups, and salads and are also served as a cooked vegetable.",
                null,
                "15,30,2",
                0,
                "sandy to loamy, loose soil, free of stones",
                new ArrayList<>(),
                List.of(
                        new GrowthPhase(MonthDay.of(6, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.HARVEST, HardinessZone.ZONE_8A, List.of(
                                exampleTaskTemplateList.get(0),
                                exampleTaskTemplateList.get(1)
                        )),
                        new GrowthPhase(MonthDay.of(4, 3), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, List.of(
                                exampleTaskTemplateList.get(2),
                                exampleTaskTemplateList.get(3)
                        ))
                )));
    }

    void createExampleTaskList() {
        exampleTaskList = new ArrayList<>();
        exampleTaskList.add(new Task("name", "description", LocalDate.now().minusDays(1), 1L));
        exampleTaskList.add(new Task("name", "description", LocalDate.now().plusDays(1), 2L));
        exampleTaskList.add(new Task("name", "description", LocalDate.now(), 1L));
        exampleTaskList.add(new Task("name", "description", LocalDate.of(9019, 5, 5), 1L));
        exampleTaskList.add(new Task("name", "description", LocalDate.now().minusDays(10), LocalDate.now().plusDays(4),2, 2L));
    }

    @Test
    void addTask() throws IOException {
        Task taskToAdd = new Task("name", "description", LocalDate.now(), 1L);
        model.addTask(taskToAdd);
        verify(taskList, times(1)).saveTask(taskToAdd);
    }

    @Test
    void removeTask() throws IOException {
        Task taskToRemove = new Task("name", "description", LocalDate.now(), 1L);
        model.removeTask(taskToRemove);
        verify(taskList, times(1)).removeTask(taskToRemove);
    }

    @Test
    void getTaskList() throws IOException {
        List<Task> listToCheck = model.getTaskList();
        assertEquals(5, listToCheck.size());
        assertEquals(exampleTaskList.get(4), listToCheck.get(0));
        assertEquals(exampleTaskList.get(0), listToCheck.get(1));
        assertEquals(exampleTaskList.get(2), listToCheck.get(2));
        assertEquals(exampleTaskList.get(1), listToCheck.get(3));
        assertEquals(exampleTaskList.get(3), listToCheck.get(4));
    }

    @Test
    void getFutureTasks() throws IOException {
        List<Task> listToCheck = model.getFutureTasks();
        assertEquals(3, listToCheck.size());
        assertEquals(exampleTaskList.get(2), listToCheck.get(0));
        assertEquals(exampleTaskList.get(1), listToCheck.get(1));
        assertEquals(exampleTaskList.get(3), listToCheck.get(2));
    }

    @Test
    void getPastTasks() throws IOException {
        List<Task> listToCheck = model.getPastTasks();
        assertEquals(3, listToCheck.size());
        assertEquals(exampleTaskList.get(4), listToCheck.get(0));
        assertEquals(exampleTaskList.get(0), listToCheck.get(1));
        assertEquals(exampleTaskList.get(2), listToCheck.get(2));
    }

    @Test
    void getTasksUpcomingWeek() throws IOException {
        List<List<Task>> dayList = model.getTasksUpcomingWeek();
        assertEquals(7, dayList.size());

        //Check day 0
        assertEquals(2, dayList.get(0).size());
        assertTrue(dayList.get(0).contains(exampleTaskList.get(2)));
        assertTrue(dayList.get(0).contains(exampleTaskList.get(4)));

        //Check day 1
        assertEquals(1, dayList.get(1).size());
        assertTrue(dayList.get(1).contains(exampleTaskList.get(1)));

        //Check day 2
        assertEquals(1, dayList.get(2).size());
        assertTrue(dayList.get(2).contains(exampleTaskList.get(4)));
        //Check day 3
        assertEquals(0, dayList.get(3).size());
        //Check day 4
        assertEquals(1, dayList.get(4).size());
        assertTrue(dayList.get(4).contains(exampleTaskList.get(4)));
        //Check day 5
        assertEquals(0, dayList.get(5).size());
        //Check day 6
        assertEquals(0, dayList.get(6).size());
    }

    @Test
    void removeTasksForCrop() throws IOException {
        model.removeTasksForCrop(1L);
        verify(taskList, times(1)).removeTasksForCrop(1L);
    }

    @Test
    void planTasksForCrop() throws HardinessZoneNotSetException, PlantNotFoundException, IOException {
        model.planTasksForCrop(new Crop(20, exampleStartDate).withId(30));
        verify(exampleTaskTemplateList.get(0), times(1)).generateTask(exampleStartDate, 30);
        verify(exampleTaskTemplateList.get(1), times(1)).generateTask(exampleStartDate, 30);
        verify(exampleTaskTemplateList.get(2), times(1)).generateTask(exampleStartDate, 30);
        verify(exampleTaskTemplateList.get(3), times(1)).generateTask(exampleStartDate, 30);
        verify(taskList, times(1)).saveTask(exampleTaskList.get(0));
        verify(taskList, times(1)).saveTask(exampleTaskList.get(1));
        verify(taskList, times(1)).saveTask(exampleTaskList.get(2));
        verify(taskList, times(1)).saveTask(exampleTaskList.get(3));
    }
}