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

import cz.muni.fi.pa165.soccermanager.dto.MatchDto;
import cz.muni.fi.pa165.soccermanager.dto.PlayerDto;
import cz.muni.fi.pa165.soccermanager.dto.UserDto;
import cz.muni.fi.pa165.soccermanager.entity.League;
import cz.muni.fi.pa165.soccermanager.entity.Team;
import cz.muni.fi.pa165.soccermanager.exception.MissingEntityReference;
import cz.muni.fi.pa165.soccermanager.mapper.MatchMapper;
import cz.muni.fi.pa165.soccermanager.mapper.UserMapper;
import cz.muni.fi.pa165.soccermanager.repository.MatchRepository;
import cz.muni.fi.pa165.soccermanager.repository.TeamRepository;
import cz.muni.fi.pa165.soccermanager.repository.UserRepository;
import cz.muni.fi.pa165.soccermanager.service.PlayerService;
import cz.muni.fi.pa165.soccermanager.service.UserService;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
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 UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;
    private final TeamRepository teamRepository;
    private final PlayerService playerService;
    private final MatchMapper matchMapper;
    private final MatchRepository matchRepository;

    @Override
    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public Page<UserDto> getAllUsers(int offset, int pageSize, String field) {
        return userRepository.findAll(PageRequest.of(offset, pageSize).withSort(Sort.by(field).descending())).map(userMapper::map);
    }
    @Override
    public Optional<UserDto> findUserById(Long userId) {
        return userRepository.findById(userId).map(userMapper::map);
    }

    @Override
    @PreAuthorize("@sc.thisUserOrAdmin(#userId)")
    public void removeTeam(Long userId) {
        final var u = userRepository.findById(userId).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        validateUser(u);
        u.getTeam().setUser(null);
        u.setTeam(null);
    }


    @Override
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public List<MatchDto> findUserMatches(Long userId) {
        final var u = userRepository.findById(userId).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        validateUser(u);
        return matchRepository.findMatchesByUserIdAndLeagueId(u.getTeam().getLeague().getId(), userId)
            .stream().map(matchMapper::map).collect(Collectors.toList());
    }

    @Override
    @PreAuthorize("hasAnyAuthority('SCOPE_USER', 'SCOPE_ADMIN')")
    public List<PlayerDto> findUserPlayers(Long userId) {
        final var u = userRepository.findById(userId).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        validateUser(u);
        return playerService.findPlayersByTeam(u.getTeam().getId());
    }

    @Override
    @PreAuthorize("@sc.thisUserOrAdmin(#user)")
    public void assignTeamToUser(Long team, Long user) {
        final var u = userRepository.findById(user).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        final var t = teamRepository.findById(team).orElseThrow(() -> {
            throw new EntityNotFoundException();
        });
        if (t.getUser() != null) {
            throw new AccessDeniedException("Team has a user");
        }
        if (t.getLeague() == null) {
            throw new MissingEntityReference(League.class);
        }
        t.setUser(u);
    }

    private void validateUser(cz.muni.fi.pa165.soccermanager.entity.User user) {
        if (user.getTeam() == null) {
            throw new MissingEntityReference(Team.class);
        }
        if (user.getTeam().getLeague() == null) {
            throw new MissingEntityReference(League.class);
        }
    }
}
