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

import cz.muni.fi.pa165.soccermanager.dto.request.UpdateTeamDto;
import cz.muni.fi.pa165.soccermanager.dto.TeamDto;
import cz.muni.fi.pa165.soccermanager.dto.request.CreateTeamRequest;
import cz.muni.fi.pa165.soccermanager.exception.NameAlreadyExistsException;
import cz.muni.fi.pa165.soccermanager.exception.NotFreeAgentException;
import cz.muni.fi.pa165.soccermanager.exception.NotInTeamException;
import cz.muni.fi.pa165.soccermanager.mapper.TeamMapper;
import cz.muni.fi.pa165.soccermanager.repository.ChampionshipRepository;
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.TeamService;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class TeamServiceImpl implements TeamService {

    private final TeamRepository teamRepository;
    private final PlayerRepository playerRepository;
    private final TeamMapper teamMapper;
    private final ChampionshipRepository championshipRepository;
    private final LeagueRepository leagueRepository;

    @Override
    @PreAuthorize("hasAuthority('SCOPE_ADMIN')")
    public void remove(Long t) {
        teamRepository.delete(teamRepository.findById(t).orElseThrow(() -> {
            throw new EntityNotFoundException();
        }));
    }

    @Override
    @PreAuthorize("@sc.teamOwnerOrAdmin(#t)")
    public void update(Long t, UpdateTeamDto dto) {
        final var team = teamRepository.findById(t).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        teamMapper.mapOnUpdate(team, dto);
    }

    @Override
    @PreAuthorize("@sc.teamOwnerOrAdmin(#t)")
    public void addFreeAgent(Long player, Long t) {
        final var team = teamRepository.findById(t).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        final var p = playerRepository.findById(player).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        if (p.getTeam() != null) {
            throw new NotFreeAgentException();
        }
        p.setTeam(team);
    }

    @Override
    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public Optional<TeamDto> findTeamById(Long teamId) {
        return teamRepository.findById(teamId).map(teamMapper::map);
    }

    @Override
    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public List<TeamDto> getAllTeamsByLeague(Long leagueId) {
        return teamRepository.findByLeague(leagueId).stream().map(teamMapper::map).collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public List<TeamDto> getAllTeamsByChampionship(Long championshipId) {
        return teamRepository.findByChampionship(championshipId).stream().map(teamMapper::map).collect(Collectors.toList());
    }

    @Override
    @PreAuthorize("hasAuthority('SCOPE_ADMIN')")
    public Long createTeam(Long leagueId, CreateTeamRequest request) {
        if (teamRepository.teamNameExists(request.getName()).equals(Boolean.TRUE)) {
            throw new NameAlreadyExistsException("Name of the team must be unique");
        }
        final var next = teamMapper.map(request);
        next.setLeague(leagueRepository.findById(leagueId).orElseThrow(() -> {
            throw new EntityNotFoundException();
        }));
        return teamRepository.saveAndFlush(next).getId();
    }

    @Override
    @PreAuthorize("@sc.teamOwnerOrAdmin(#teamID)")
    public void removePlayer(Long teamID, Long playerId) {
        final var t = teamRepository.findById(teamID).orElseThrow(() -> {
                throw new EntityNotFoundException();
            });

        final var p = playerRepository.findById(playerId).orElseThrow(() -> {
                throw new EntityNotFoundException();
            });
        if (!p.getTeam().getId().equals(t.getId())) {
            throw new NotInTeamException();
        }
        p.setTeam(null);
    }

    @Override
    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public Optional<TeamDto> findTeamByUserId(Long userId) {
        return teamRepository.findByUserId(userId).map(teamMapper::map);
    }

}
