package enea.lecservice.services;

import enea.lecservice.models.dto.ServiceResponse;
import enea.lecservice.repository.PermissionRepository;
import enea.lecservice.security.IUserInfoService;
import enea.lecservice.security.dto.UserInfo;
import enea.lecservice.utils.JacksonUtils;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;

import java.security.Principal;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Log4j2
@Profile("prod")
@RequiredArgsConstructor
public class EneaInternalUserInfoService implements IUserInfoService {
    protected final static String KEY_USERNAME = "username";
    protected final static String KEY_PASSWORD = "password";
    protected final static String KEY_TOKEN = "token";

    @Value("${data.api.baseurl}")
    private String apiHost;

    @Value("${authentication.login}")
    private String loginUrl;

    @Value("${authentication.userFromToken}")
    private String userFromTokenUrl;
        
    @Value("${authentication.logout}")
    private String logoutUrl;
    
    @Value("${authentication.secret}")
    private String clientSecret;

    @NonNull PermissionRepository permissionRepository;

    @Override
    public Optional<UserInfo> getUserInfo(Principal principal) {
        UserInfo userInfo = (UserInfo) ((UsernamePasswordAuthenticationToken) principal).getPrincipal();
        if (userInfo != null)
            return Optional.of(userInfo);

        return Optional.empty();
    }

    @Override
    public Optional<UserInfo> getUserInfo(String token) {
        RestTemplate restTemplate = initRestTemplate();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("token",token);
        map.add("clientSecret", clientSecret);

        try {
            ResponseEntity<UserInfo> clientResponse = restTemplate.postForEntity(userFromTokenUrl, map, UserInfo.class);
            if (clientResponse.getStatusCode().is2xxSuccessful()) {
                UserInfo userInfo = (UserInfo) clientResponse.getBody();
                if (userInfo != null) {
                    log.info(JacksonUtils.toJson(userInfo));
                    userInfo.setUsername(userInfo.getEmail());
                    fillPermissionsByRoles(userInfo);
                    return Optional.of(userInfo);
                }
            }
        }
        catch (Exception ex) {
            
            log.error(ex);
        }

        return Optional.empty();
    }
        
    
    @Override
    public Optional<UserInfo> getUserInfo(String username, String password) {
        RestTemplate restTemplate = initRestTemplate();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add(KEY_USERNAME,username);
        map.add(KEY_PASSWORD,password);
        map.add("clientSecret", clientSecret);

        try {
            ResponseEntity<UserInfo> clientResponse = restTemplate.postForEntity(loginUrl, map, UserInfo.class);
            if (clientResponse.getStatusCode().is2xxSuccessful()) {
                UserInfo userInfo = (UserInfo) clientResponse.getBody();
                if (userInfo != null) {
                    log.info(JacksonUtils.toJson(userInfo));
                    userInfo.setUsername(username);
                    userInfo.setPassword(password);
                    fillPermissionsByRoles(userInfo);
                    return Optional.of(userInfo);
                }
            }
        }
        catch (Exception ex) {
            // TODO: If response codes should be managed, handle them here!
            log.error(ex);
        }

        return Optional.empty();
    }

    private void fillPermissionsByRoles(UserInfo userInfo) {
        //var roles = userInfo.getUserroles().stream().map(o -> o.getRole().getRolename()).collect(Collectors.toList());
        var roles = userInfo.getUserroles().stream().map(o -> o.getRoleName()).collect(Collectors.toList());
        var permissions = permissionRepository.findPermissionsByRolesDescription(roles);
        userInfo.setPermissions(permissions);
    }

    @Override
    public ServiceResponse<?> logout(UserInfo userInfo) {
        if (userInfo == null || userInfo.getToken() == null || (userInfo.getToken().getValue() == null && userInfo.getToken().getValue().isEmpty()))
            return ServiceResponse.Error("Fornisci un token valido!");

        RestTemplate restTemplate = initRestTemplate();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add(KEY_TOKEN, userInfo.getToken().getValue());
        map.add("clientSecret", clientSecret);
        
        ServiceResponse<?> result;

        try {
            ResponseEntity<Boolean> clientResponse = restTemplate.postForEntity(logoutUrl, map, Boolean.class);
            if (clientResponse.getStatusCode().is2xxSuccessful()) {
                if (clientResponse.getBody())
                    result = ServiceResponse.Success();
                else
                    result = ServiceResponse.Error("Impossibile eseguire il logout");
            }
            else {
                result = ServiceResponse.Error(clientResponse.getStatusCode().getReasonPhrase());
            }
        }
        catch (Exception ex) {
            result = ServiceResponse.Error(ex);
        }

        return result;
    }

    private RestTemplate initRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(apiHost));
        return restTemplate;
    }
}
