package com.sn.sowsysrestapi.domain.service;

import com.sn.sowsysrestapi.domain.entity.ReportDirectory;
import com.sn.sowsysrestapi.domain.entity.User;
import com.sn.sowsysrestapi.domain.exception.BusinessException;
import com.sn.sowsysrestapi.domain.exception.EntityInUseException;
import com.sn.sowsysrestapi.domain.exception.UserNotFoundException;
import com.sn.sowsysrestapi.domain.repository.ReportDirectoryRepo;
import com.sn.sowsysrestapi.domain.repository.UserRepo;
import lombok.AllArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;

@Service
@AllArgsConstructor
public class UserService {

    public static final String MSG_ENTITY_IN_USE = "User with ID %s can't be deleted. " +
            "Reason: Other entity is using this entity.";

//    @Autowired
    private UserRepo userRepo;

//    @Autowired
    private ReportDirectoryService reportDirectoryService;

//    @Autowired
    private ReportDirectoryRepo reportDirectoryRepo;

//    @Autowired
    private SendEmailService sendEmail;

    private PasswordEncoder passwordEncoder;

    @Transactional
    public User save(User user) {

        userRepo.detach(user);

        Optional<User> existentUser = userRepo.findByUsernameIgnoreCase(user.getUsername());

        if (existentUser.isPresent()) {
            throw new BusinessException(
                    String.format("There is already an user registered with the username '%s'.", user.getUsername())
            );
        }

        existentUser = userRepo.findByEmail(user.getEmail());

        if (existentUser.isPresent()) {
            throw new BusinessException(
                    String.format("There is already an user registered with the email '%s'.", user.getEmail())
            );
        }

        user.setIsActiveted(false);
        user.setPassword(encodePassword(user));

        user = userRepo.save(user);


        if (user.getReportDirectory() == null) {

            ReportDirectory reportDirectory = new ReportDirectory();
            reportDirectory.setOwnerCode(user.getUserCode());
            reportDirectory.setOwnerUsername(user.getUsername());
            reportDirectory.setReportList(new ArrayList<>());
            reportDirectory.setSupervisorCode(new HashSet<>());
            reportDirectoryService.save(reportDirectory);

            user.setReportDirectory(reportDirectory);

        }

        return userRepo.save(user);

    }

    @Transactional
    public User update(User user) {

        return userRepo.save(user);

    }

    @Transactional
    public void sendEmailWithActivationCode(String userCode) {

        try {
            Optional<User> user = userRepo.findByUserCode(userCode);

            var message = SendEmailService.Message.builder()
                    .subject("Activation Code for SOW App")
                    // .body("Your activation code is: " + user.get().getActivationCode())
                    .body("activation-code.html")
                    .variable("userFullName", user.get().getFirstName() + " " + user.get().getLastName())
                    .variable("activationCode", user.get().getActivationCode())
                    .variable("userCode", user.get().getUserCode())
                    .recipient(user.get().getEmail())
                    .build();

            sendEmail.send(message);

        } catch (EmptyResultDataAccessException e) {
            throw new UserNotFoundException(userCode);
        }

    }

    public void activateAccount(String userCode, String activationCode) throws Exception {

        try {
            Optional<User> user = userRepo.findByUserCode(userCode);

            if (Objects.equals(user.get().getActivationCode(), activationCode)) {

                user.get().activateAccount();

                User activeUser = user.get();

                update(activeUser);

            } else {
                throw new Exception("The activation code that was informed does not match the user records. " +
                        "Please check if the activation code you entered match with the one that was sent to " +
                        user.get().getEmail() + " and try again.");
            }

        } catch (EmptyResultDataAccessException e) {
            throw new UserNotFoundException(userCode);

        }

    }

    public void delete(String userCode) {

        try {
            userRepo.deleteByUserCode(userCode);
            userRepo.flush();

        } catch (EmptyResultDataAccessException e) {
            throw new UserNotFoundException(userCode);

        } catch (DataIntegrityViolationException e) {
            throw new EntityInUseException(
                    String.format(MSG_ENTITY_IN_USE, userCode));
        }

    }

    @Transactional
    public void changePassword(String userCode, String currentPassword, String newPassword) {

        User user = findOrFail(userCode);

//            if (user.passwordDoesNotMatchWith(currentPassword)) {
//                throw new BusinessException(
//                        String.format("The entered password: %s does not match with the password associated with the " +
//                                "username: %s", currentPassword, user.getUsername())
//                );
//            }
//
//        user.setPassword(newPassword);

        if (passwordEncoder.matches(currentPassword, user.getPassword())) {
            user.setPassword(passwordEncoder.encode(newPassword));

        } else if (!passwordEncoder.matches(currentPassword, user.getPassword())) {
            throw new BusinessException(
                    String.format("The entered password: %s does not match with the password associated with the " +
                            "username: %s", currentPassword, user.getUsername())
            );
        }

    }

    public User findOrFail(String userCode) {
        return userRepo.findByUserCode(userCode)
                .orElseThrow(() -> new UserNotFoundException(userCode));
    }

    private String encodePassword(User user) {
        return passwordEncoder.encode(user.getPassword());
    }


}
