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

import cz.muni.fi.pa165.soccermanager.dto.MatchDto;
import cz.muni.fi.pa165.soccermanager.dto.MatchScoreDto;
import cz.muni.fi.pa165.soccermanager.dto.request.CreateMatchRequest;
import cz.muni.fi.pa165.soccermanager.dto.request.UpdateMatchDto;
import cz.muni.fi.pa165.soccermanager.entity.League;
import cz.muni.fi.pa165.soccermanager.entity.Match;
import cz.muni.fi.pa165.soccermanager.entity.Team;
import cz.muni.fi.pa165.soccermanager.mapper.MatchMapper;
import cz.muni.fi.pa165.soccermanager.repository.LeagueRepository;
import cz.muni.fi.pa165.soccermanager.repository.MatchRepository;
import cz.muni.fi.pa165.soccermanager.repository.TeamRepository;
import cz.muni.fi.pa165.soccermanager.service.impl.MatchServiceImpl;
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.Optional;

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.*;


/**
 * @author Petr Adamec (485584)
 */
@ExtendWith(MockitoExtension.class)
public class MatchServiceTest {

    @Mock
    private MatchRepository matchRepository;

    @Mock
    private LeagueRepository leagueRepository;

    @Mock
    private TeamRepository teamRepository;

    @Mock
    private MatchMapper matchMapper;

    @InjectMocks
    private MatchServiceImpl matchService;

    private final LocalDate START_DATE1 = LocalDate.of(2022, 1, 13);
    private final LocalDate START_DATE2 = LocalDate.of(2022, 2, 14);
    private Match match;
    private MatchDto matchDto;
    private Team team1;
    private Team team2;

    @BeforeEach
    public void setup() {
        match = new Match();
        match.setId(1L);
        match.setStartDate(START_DATE1);

        matchDto = new MatchDto();
        matchDto.setId(2L);
        matchDto.setStartDate(START_DATE2);

        team1 = new Team();
        team1.setId(10L);
        team1.setName("name1");
        team1.setPoints(1);
        team1.setGround("ground2");
        League league = new League();
        league.setId(20L);
        team1.setLeague(league);

        team2 = new Team();
        team2.setId(11L);
        team2.setName("name2");
        team2.setPoints(2);
        team2.setGround("ground2");
    }

    @Test
    public void updateTest() {
        UpdateMatchDto matchDto = new UpdateMatchDto();
        match.setStartDate(START_DATE2);
        when(matchRepository.findById(match.getId())).thenReturn(Optional.of(match));
        matchService.update(1L,matchDto);

        verify(matchMapper, times(1)).mapOnUpdate(match,matchDto);
    }

    @Test
    public void removeTest() {
        when(matchRepository.findById(match.getId())).thenReturn(Optional.of(match));
        matchService.remove(match.getId());

        verify(matchRepository, times(1)).delete(match);
    }

    @Test
    public void saveMatchTest() {
        final var match = new CreateMatchRequest();
        when(matchRepository.saveAndFlush(any(Match.class))).thenReturn(new Match());
        when(matchMapper.map(any(CreateMatchRequest.class))).thenReturn(new Match());
        when(leagueRepository.findById(any(Long.class))).thenReturn(Optional.of(new League()));
        matchService.saveMatch(match,1L);

        verify(matchMapper).map(match);
        verify(matchRepository, times(1)).saveAndFlush(any(Match.class));
    }

    @ParameterizedTest
    @ValueSource(longs = {1L, 2L, 3L})
    public void findMatchByIdTest(Long id) {
        when(matchRepository.findById(any(Long.class))).thenReturn(Optional.of(match));
        when(matchMapper.map(any(Match.class))).thenReturn(matchDto);

        assertTrue(matchService.findMatchById(id).isPresent());

        final var match2 = ArgumentCaptor.forClass(Match.class);

        verify(matchRepository, times(1)).findById(id);
        verify(matchMapper, times(1)).map(match2.capture());

        assertEquals(match, match2.getValue());
    }

    @Test
    public void playMatchTest() {
        match.setDefender(team1);
        match.setChallenger(team2);

        when(matchRepository.findById(match.getId())).thenReturn(Optional.of(match));
        final MatchScoreDto result = matchService.playMatch(match.getId());

        assertEquals(result.getChallengerGoals(), match.getChallengerGoals());
        assertEquals(result.getDefenderGoals(), match.getDefenderGoals());
    }

    @Test
    public void getMatchesByTeamIdTest() {
        match.setDefender(team1);

        when(teamRepository.findById(team1.getId())).thenReturn(Optional.of(team1));
        matchService.getMatchesByTeamId(team1.getId());

        verify(matchRepository, times(1)).findMatchesByTeamIdAndLeagueId(team1.getId(),
                team1.getLeague().getId());
    }

    @Test
    public void findMatchesByLeagueTest() {
        League league = new League();
        league.setId(1L);
        league.setName("name");
        league.setStartDate(LocalDate.of(2022, 1, 13));
        league.setEndDate(LocalDate.of(2022, 2, 13));
        match.setLeague(league);

        matchService.findMatchesByLeague(league.getId());
        verify(matchRepository, times(1)).findByLeagueId(league.getId());
    }
}

