package de.szut.casino.security.oauth2;

import de.szut.casino.exceptionHandling.exceptions.OAuth2AuthenticationProcessingException;
import de.szut.casino.user.AuthProvider;
import de.szut.casino.user.UserEntity;
import de.szut.casino.user.UserRepository;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Optional;
import java.util.UUID;

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder oauth2PasswordEncoder;

    public CustomOAuth2UserService(UserRepository userRepository, PasswordEncoder oauth2PasswordEncoder) {
        this.userRepository = userRepository;
        this.oauth2PasswordEncoder = oauth2PasswordEncoder;
    }

    @Override
    public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);

        try {
            return processOAuth2User(oAuth2UserRequest, oAuth2User);
        } catch (AuthenticationException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause());
        }
    }

    private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
        String registrationId = oAuth2UserRequest.getClientRegistration().getRegistrationId();
        OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User.getAttributes());

        String email = oAuth2UserInfo.getEmail();
        if (StringUtils.isEmpty(email)) {
            email = oAuth2UserInfo.getName() + "@github.user";
        }

        Optional<UserEntity> userOptional = userRepository.findByEmail(email);
        UserEntity user;

        if (userOptional.isPresent()) {
            user = userOptional.get();

            if (!user.getProvider().equals(AuthProvider.valueOf(registrationId.toUpperCase()))) {
                throw new OAuth2AuthenticationProcessingException("You're signed up with " +
                        user.getProvider() + ". Please use your " + user.getProvider() +
                        " account to login.");
            }

            user = updateExistingUser(user, oAuth2UserInfo);
        } else {
            user = registerNewUser(oAuth2UserRequest, oAuth2UserInfo, email);
        }

        return UserPrincipal.create(user, oAuth2User.getAttributes());
    }

    private UserEntity registerNewUser(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo, String email) {
        UserEntity user = new UserEntity();

        String username = oAuth2UserInfo.getName();
        if (StringUtils.isEmpty(username)) {
            username = "github_" + oAuth2UserInfo.getId();
        }

        if (userRepository.findByUsername(username).isPresent()) {
            username = username + "_" + UUID.randomUUID().toString().substring(0, 8);
        }

        user.setProvider(AuthProvider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId().toUpperCase()));
        user.setProviderId(oAuth2UserInfo.getId());
        user.setUsername(username);
        user.setEmail(email);
        user.setEmailVerified(true);

        String randomPassword = UUID.randomUUID().toString();
        user.setPassword(oauth2PasswordEncoder.encode(randomPassword));

        user.setBalance(new BigDecimal("100.00")); // Starting balance

        return userRepository.save(user);
    }

    private UserEntity updateExistingUser(UserEntity existingUser, OAuth2UserInfo oAuth2UserInfo) {
        if (!StringUtils.isEmpty(oAuth2UserInfo.getName())) {
            existingUser.setUsername(oAuth2UserInfo.getName());
        }
        return userRepository.save(existingUser);
    }
}
