package io.gmss.fiscad.implementations.user;

import io.gmss.fiscad.entities.infocad.parametre.Structure;
import io.gmss.fiscad.entities.user.Role;
import io.gmss.fiscad.entities.user.User;
import io.gmss.fiscad.enums.UserRole;
import io.gmss.fiscad.exceptions.BadRequestException;
import io.gmss.fiscad.exceptions.NotFoundException;
import io.gmss.fiscad.interfaces.infocad.parametre.StructureService;
import io.gmss.fiscad.interfaces.user.RoleService;
import io.gmss.fiscad.interfaces.user.UserService;
import io.gmss.fiscad.paylaods.JwtAuthenticationResponse;
import io.gmss.fiscad.paylaods.Login;
import io.gmss.fiscad.paylaods.UserListByStructureResponse;
import io.gmss.fiscad.paylaods.UserResponse;
import io.gmss.fiscad.repositories.user.UserRepository;
import io.gmss.fiscad.security.TokenAuthentificationProvider;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;


@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final RoleService roleService;
    private final AuthenticationManager authenticationManager;
    private final TokenAuthentificationProvider tokenAuthentificationProvider;
    private final StructureService structureService;

    public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, RoleService roleService, AuthenticationManager authenticationManager, TokenAuthentificationProvider tokenAuthentificationProvider, StructureService structureService) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.roleService = roleService;
        this.authenticationManager = authenticationManager;
        this.tokenAuthentificationProvider = tokenAuthentificationProvider;
        this.structureService = structureService;
    }


    @Override
    public User createUser(User user, boolean resetPassword) {

        if (userRepository.findByUsername(user.getUsername()).isPresent()) {
            throw new BadRequestException("Ce nom d'utilisateur existe déjà.");
        }

        if (user.getId() != null) {
            throw new BadRequestException("Cet utilisateur existe déjà dans la base de donnéees.");
        }

        Set<Role> roleSet = new HashSet<>();

        user.getRoles().stream().forEach(role -> {
            if (roleService.roleExistByRoleName(role.getNom())) {
                roleSet.add(roleService.retrieveRoleByRoleName(role.getNom()));
            }
        });
        user.setUsername(user.getEmail());
        user.setResetPassword(resetPassword);
        user.setRoles(roleSet);
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        userRepository.save(user);
        return user;
    }

    @Override
    public JwtAuthenticationResponse loginUser(Login login) {

        User user = getUserByUsername(login.getUsername());

        if (user == null) {
            throw new NotFoundException("Impossible de trouver l'utilisateur spécifié avec le nom d'utilisateur " + login.getUsername() + ".");
        }

        if (!user.isActive()) {
            throw new BadRequestException("Le compte de l' utilisateur " + login.getUsername() + "  n'est pas actif.");
        }

        Authentication authentication = authenticationManager
                .authenticate(
                        new UsernamePasswordAuthenticationToken(login.getUsername(),
                                login.getPassword())
                );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        return tokenAuthentificationProvider.generateToken(authentication);
    }

    @Override
    public User updateUser(Long id, User user) {
        User user1 = userRepository.findById(id)
                .orElseThrow(() -> new NotFoundException(
                                String.format("L'utilisateur ayant pour id %s n'existe pas.", id)
                        )
                );
        if (user.getRoles() == null || user.getRoles().isEmpty()) {
            user.setRoles(user1.getRoles());
        }

        if (user.getPassword() == null || user.getPassword().isBlank()) {
            user.setPassword(user1.getPassword());
        } else {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }

        if (user.getEmail() == null || user.getEmail().isEmpty()) {
            user.setEmail(user1.getEmail());
        } else {
            user.setEmail(user.getEmail());
        }
        user.setResetPassword(user1.isResetPassword());
        return userRepository.save(user);
    }

    @Override
    public void updatePassword(String username, String pwd) {
        User user = userRepository.findByUsername(username).orElseThrow(() -> new NotFoundException(
                String.format("L'utilisateur %s n'existe pas.", username)
        ));
        user.setUsername(username);
        user.setResetPassword(false);
        user.setPassword(passwordEncoder.encode(pwd));
        userRepository.save(user);
    }

    @Override
    public void deleteUser(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new NotFoundException(
                                String.format("L'utilisateur ayant pour id %s n'existe pas.", id)
                        )
                );
        userRepository.delete(user);
    }

    @Override
    public Page<User> getUserList(Pageable pageable) {
        return userRepository.findAll(pageable);
    }

    @Override
    public List<User> getUserList() {
        return userRepository.findAll();
    }

    @Override
    public List<UserResponse> getAllUserListResponse() {
        return getUserResponses(getUserList());
    }

    @Override
    public List<User> getActivatedUserListByStructure(Long structureId) {
        Set<Role> roleSet = new HashSet<>();
        roleSet.add(roleService.retrieveRoleByRoleName(UserRole.ROLE_ANONYMOUS));
        Optional<Structure> structureOptional = structureService.getStructureById(structureId);
        if (structureOptional.isPresent()) {
            return userRepository.findAllByStructureAndRolesNotIn(structureOptional.get(), roleSet);
        } else {
            throw new NotFoundException("Impossible de trouver la structure spécifiée.");
        }
    }

    @Override
    public List<User> getUserUnActivatedListByStructure(Long structureId) {
        Set<Role> roleSet = new HashSet<>();
        roleSet.add(roleService.retrieveRoleByRoleName(UserRole.ROLE_ANONYMOUS));
        Optional<Structure> structureOptional = structureService.getStructureById(structureId);
        if (structureOptional.isPresent()) {
            return userRepository.findAllByStructureAndRolesIn(structureOptional.get(), roleSet);
        } else {
            throw new NotFoundException("Impossible de trouver la structure spécifiée.");
        }
    }

    private static List<UserResponse> getUserResponses(List<User> users) {
        return users.stream()
                .map(user -> new UserResponse(
                        user.getId(),
                        user.getNom(),
                        user.getPrenom(),
                        user.getTel(),
                        user.getEmail(),
                        user.getUsername(),
                        user.isActive(),
                        user.isResetPassword(),
                        user.getRoles(),
                        user.getStructure(),
                        user.getIdCampagneCourant(),
                        user.getIdSecteurCourant()))
                .collect(Collectors.toList());
    }


    @Override
    public long countUser() {
        return userRepository.count();
    }

    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow(() -> new NotFoundException(
                        String.format("L'utilisateur ayant pour id %s n'existe pas.", id)
                )
        );
    }

    @Override
    public List<User> getUserByProfil(UserRole userRole) {
        return userRepository.findAllByRolesContains(userRole);
    }


    @Override
    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username).orElseThrow(() -> new NotFoundException(
                        String.format("L'utilisateur ayant pour nom %s n'existe pas.", username)
                )
        );
    }

    @Override
    public User activateOrNotUser(Long id) {
        User user = getUserById(id);
        if (user.isActive()) {
            user.setActive(false);
        }

        if (!user.isActive()) {
            user.setActive(true);
        }
        return userRepository.save(user);
    }

    @Override
    public User resetPassword(String username, String password) {
        User user = userRepository.findByUsername(username).orElseThrow(() -> new NotFoundException(
                String.format("L'utilisateur %s n'existe pas.", username)
        ));
        //String pwd = PasswordGenerator.generatePassword();
        user.setPassword(passwordEncoder.encode(password));
        user.setResetPassword(true);
        userRepository.save(user);
        return user;
    }

    @Override
    public User assignStructureToUser(Structure structure) {
        return null;
    }

    @Override
    public User validateUserAccount(String username, UserRole userRole) {
        User user = userRepository.findByUsername(username).orElseThrow(() -> new NotFoundException(
                String.format("L'utilisateur %s n'existe pas.", username)
        ));
        Set<Role> roleSet = new HashSet<>();
        if (roleService.roleExistByRoleName(userRole)) {
            roleSet.add(roleService.retrieveRoleByRoleName(userRole));
        }
        user.setRoles(roleSet);
        user.setResetPassword(false);
        return userRepository.save(user);
    }

    @Override
    public UserListByStructureResponse getListUserByStructure(Long structureId) {
        return new UserListByStructureResponse(getUserResponses(getActivatedUserListByStructure(structureId)), getUserResponses(getUserUnActivatedListByStructure(structureId)));
    }

    @Override
    public List<UserResponse> getListUserResponseByStructure(Long structureId) {
        List<UserResponse> userResponses = new ArrayList<>();
        userResponses.addAll(getUserResponses(getActivatedUserListByStructure(structureId)));
        userResponses.addAll(getUserResponses(getUserUnActivatedListByStructure(structureId)));
        return userResponses;
    }

    @Override
    public UserResponse getUserResponseFromUser(User user) {
        return new UserResponse(
                user.getId(),
                user.getNom(),
                user.getPrenom(),
                user.getTel(),
                user.getEmail(),
                user.getUsername(),
                user.isActive(),
                user.isResetPassword(),
                user.getRoles(),
                user.getStructure(),
                user.getIdCampagneCourant(),
                user.getIdSecteurCourant());

    }

//    private static List<UserResponse> getUserResponses(List<User> users) {
//        return users.stream()
//                .map(user -> new UserResponse(
//                        user.getId(),
//                        user.getNom(),
//                        user.getPrenom(),
//                        user.getTel(),
//                        user.getEmail(),
//                        user.getUsername(),
//                        user.isActive(),
//                        user.isResetPassword(),
//                        user.getRoles(),
//                        user.getStructure()))
//                .collect(Collectors.toList());
//    }
}
