package cz.muni.fi.pa165.soccermanager.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import cz.muni.fi.pa165.soccermanager.dto.TeamDto;
import cz.muni.fi.pa165.soccermanager.dto.request.CreateTeamRequest;
import cz.muni.fi.pa165.soccermanager.dto.request.UpdateTeamDto;
import cz.muni.fi.pa165.soccermanager.entity.Championship;
import cz.muni.fi.pa165.soccermanager.entity.League;
import cz.muni.fi.pa165.soccermanager.entity.Player;
import cz.muni.fi.pa165.soccermanager.entity.Team;
import cz.muni.fi.pa165.soccermanager.mapper.TeamMapper;
import cz.muni.fi.pa165.soccermanager.repository.LeagueRepository;
import cz.muni.fi.pa165.soccermanager.repository.PlayerRepository;
import cz.muni.fi.pa165.soccermanager.repository.TeamRepository;
import cz.muni.fi.pa165.soccermanager.service.impl.TeamServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author Petr Balazy (uco: 527362)
 */
@ExtendWith(MockitoExtension.class)
public class TeamServiceTest {

    @Mock
    private TeamRepository teamRepository;

    @Mock
    private TeamMapper teamMapper;

    @Mock
    private LeagueRepository leagueRepository;

    @Mock
    private PlayerRepository playerRepository;

    @InjectMocks
    private TeamServiceImpl teamService;

    private Team team;
    private TeamDto teamDto;

    @BeforeEach
    public void setup() {

        team = new Team();
        team.setId(1L);
        team.setName("name1");
        team.setPoints(1);
        team.setGround("ground1");

        teamDto = new TeamDto();
        teamDto.setId(2L);
        teamDto.setName("name2");
        teamDto.setGround("ground2");
    }

    @Test
    public void removeTest() {
        when(teamRepository.findById(team.getId())).thenReturn(Optional.of(team));
        teamService.remove(team.getId());

        verify(teamRepository, times(1)).delete(team);
    }

    @Test
    public void updateTest() {
        when(teamRepository.findById(team.getId())).thenReturn(Optional.of(team));

        UpdateTeamDto updateTeamDto = new UpdateTeamDto();
        updateTeamDto.setId(teamDto.getId());
        updateTeamDto.setName(teamDto.getName());
        updateTeamDto.setGround(teamDto.getGround());

        teamService.update(1L,updateTeamDto);
        verify(teamMapper, times(1)).mapOnUpdate(team, updateTeamDto);
    }

    @Test
    public void addFreeAgentTest() {
        Player player = new Player();
        player.setId(1L);

        when(teamRepository.findById(team.getId())).thenReturn(Optional.of(team));
        when(playerRepository.findById(player.getId())).thenReturn(Optional.of(player));
        teamService.addFreeAgent(player.getId(), team.getId());
        assertEquals(team, player.getTeam());
    }

    @ParameterizedTest
    @ValueSource(longs = {1L, 2L, 3L})
    public void findTeamByIdTest(Long id) {
        when(teamRepository.findById(any(Long.class))).thenReturn(Optional.of(team));
        when(teamMapper.map(any(Team.class))).thenReturn(teamDto);

        assertTrue(teamService.findTeamById(id).isPresent());

        final var t1 = ArgumentCaptor.forClass(Team.class);

        verify(teamRepository, times(1)).findById(id);
        verify(teamMapper, times(1)).map(t1.capture());

        assertEquals(team, t1.getValue());
    }

    @Test
    public void getAllTeamsByChampionshipTest() {
        Championship championship = new Championship();
        championship.setId(1L);
        team.setChampionship(championship);

        List<Team> teams = new ArrayList<>();
        teams.add(team);

        List<TeamDto> teamDtoList;
        teamDtoList = teams.stream().map(teamMapper::map).collect(Collectors.toList());

        when(teamRepository.findByChampionship(championship.getId())).thenReturn(teams);
        assertEquals(teamService.getAllTeamsByChampionship(championship.getId()), teamDtoList);
    }

    @Test
    public void createTeamTest() {
        final var team = new CreateTeamRequest();
        when(teamRepository.saveAndFlush(any(Team.class))).thenReturn(new Team());
        when(teamMapper.map(any(CreateTeamRequest.class))).thenReturn(new Team());
        when(leagueRepository.findById(any(Long.class))).thenReturn(Optional.of(new League()));
        teamService.createTeam(1L,team);
        verify(teamMapper).map(team);
        verify(teamRepository, times(1)).saveAndFlush(any(Team.class));
    }

    @Test
    public void removePlayerTest() {
        Player player = new Player();
        player.setId(1L);
        player.setName("1");
        player.setBirthDate(LocalDate.of(1996, 1, 13));
        player.setHeight(180.0);
        player.setWeight(80.0);
        player.setTeam(team);
        when(teamRepository.findById(any(Long.class))).thenReturn(Optional.of(team));
        when(playerRepository.findById(any(Long.class))).thenReturn(Optional.of(player));
        teamService.removePlayer(team.getId(), player.getId());

        verify(teamRepository, times(1)).findById(team.getId());
    }

    @Test
    public void findTeamByUserIdTest() {
        when(teamRepository.findByUserId(any(Long.class))).thenReturn(Optional.of(team));
        when(teamMapper.map(any(Team.class))).thenReturn(teamDto);

        assertTrue(teamService.findTeamByUserId(1L).isPresent());

        final var team2 = ArgumentCaptor.forClass(Team.class);

        verify(teamRepository, times(1)).findByUserId(1L);
        verify(teamMapper, times(1)).map(team2.capture());

        assertEquals(team, team2.getValue());
    }
}
