package org.fuseri.modulelanguageschool.course;

import org.fuseri.modulelanguageschool.user.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.server.ResponseStatusException;

import java.util.*;

import static org.mockito.Mockito.*;

@SpringBootTest
final class CourseServiceTest {

    @MockBean
    private CourseRepository courseRepository;

    @Autowired
    private CourseService courseService;

    private final Course course =  new Course("AJ1", 10,
            Language.ENGLISH, ProficiencyLevel.A1, new ArrayList<>(), false);

    private final User user = new User("novakovat", "novakova@gamil.com", "Tereza",
            "Nováková", new HashSet<>(), new HashMap<>()); // student
    private final Course courseWithEnrolledStudent = new Course("AJ1", 10,
            Language.ENGLISH, ProficiencyLevel.A1, new ArrayList<>(List.of(user)), false);
    private final List<Course> courses = List.of(course, course);

    @Test
    void save() {
        when(courseRepository.save(course)).thenReturn(course);

        Course result = courseService.save(course);

        Assertions.assertEquals(course, result);
        verify(courseRepository).save(course);
    }

    @Test
    void notFoundById() {
        when(courseRepository.findById(anyLong())).thenReturn(Optional.empty());

        Assertions.assertThrows(ResponseStatusException.class, () -> courseService.findById(anyLong()));
    }

    @Test
    void findById() {
        when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course));

        Course result = courseService.findById(anyLong());

        Assertions.assertEquals(course, result);
        verify(courseRepository).findById(anyLong());
    }

    @Test
    void findAll() {
        Pageable pageable = PageRequest.of(0, 10);
        Page<Course> page = new PageImpl<>(Collections.emptyList(), pageable, 0);

        when(courseRepository.findAll(pageable)).thenReturn(page);

        Page<Course> result = courseService.findAll(pageable);

        Assertions.assertEquals(page, result);
        verify(courseRepository).findAll(pageable);
    }

    @Test
    void update() {
        Long id = 1L;
        when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course));
        when(courseRepository.save(course)).thenReturn(course);

        Course result = courseService.update(id, course);

        Assertions.assertEquals(course, result);
        verify(courseRepository).findById(anyLong());
        verify(courseRepository).save(course);
    }

    @Test
    void updateIdDoesNotExist() {
        Long id = 1L;
        when(courseRepository.findById(anyLong())).thenReturn(Optional.empty());

        Assertions.assertThrows(ResponseStatusException.class, () -> courseService.update(id, course));
    }

    @Test
    void delete() {
        Long id = 1L;
        courseService.delete(id);

        verify(courseRepository).deleteById(id);
    }

    @Test
    void findAllByLanguage() {
        Language language = Language.ENGLISH;
        when(courseRepository.findAllByLang(any(Language.class))).thenReturn(courses);

        List<Course> result = courseService.findAll(language);

        Assertions.assertEquals(courses, result);
        verify(courseRepository).findAllByLang(language);
    }

    @Test
    void findAllByLanguageAndProf() {
        Language language = Language.ENGLISH;
        ProficiencyLevel proficiencyLevel = ProficiencyLevel.B1;

        when(courseRepository.findAllByLangProf(any(Language.class), any(ProficiencyLevel.class))).thenReturn(courses);

        List<Course> result = courseService.findAll(language, proficiencyLevel);

        Assertions.assertEquals(courses, result);
        verify(courseRepository).findAllByLangProf(language, proficiencyLevel);
    }

    @Test
    void enrol() {
        Long id = 1L;
        when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course));
        when(courseRepository.save(any(Course.class))).thenReturn(courseWithEnrolledStudent);

        Course result = courseService.enrol(id, user);

        Assertions.assertEquals(courseWithEnrolledStudent, result);
        verify(courseRepository).findById(id);
        verify(courseRepository).save(any(Course.class));
    }

    @Test
    void enrolIdDoesNotExist() {
        Long id = 1L;
        when(courseRepository.findById(anyLong())).thenReturn(Optional.empty());

        Assertions.assertThrows(ResponseStatusException.class, () -> courseService.enrol(id, user));
    }

    @Test
    void expel() {
        Long id = 1L;
        when(courseRepository.findById(anyLong())).thenReturn(Optional.of(courseWithEnrolledStudent));
        when(courseRepository.save(any(Course.class))).thenReturn(course);

        Course result = courseService.expel(id, user);

        Assertions.assertEquals(course, result);
        verify(courseRepository).findById(id);
        verify(courseRepository).save(any(Course.class));
    }

    @Test
    void expelIdDoesNotExist() {
        Long id = 1L;
        when(courseRepository.findById(anyLong())).thenReturn(Optional.empty());

        Assertions.assertThrows(ResponseStatusException.class, () -> courseService.expel(id, user));
    }

}
