package eu.dnetlib.openaire.usermanagement.api;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import eu.dnetlib.openaire.user.dao.SQLMigrationUserDAO;
import eu.dnetlib.openaire.user.ldap.MUserActionsLDAP;
import eu.dnetlib.openaire.user.pojos.migration.LDAPUser;
import eu.dnetlib.openaire.user.store.DataSourceConnector;
import eu.dnetlib.openaire.usermanagement.authorization.AuthoritiesMapper;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.mitre.openid.connect.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by sofia on 24/11/2016.
 */
@Component(value = "test3service")
@Path("/users")
public class Test3Service {

    private static final Logger logger = Logger.getLogger(Test3Service.class);

    public static final String errorMessage = "{ \"status\" : \"error\", \"code\" : \"%s\", \"message\" : \"%s\", \"description\" : \"%s\" }";


    @Autowired
    private SQLMigrationUserDAO sqlMigrationUserDAO;

    @Autowired
    private MUserActionsLDAP mUserActionsLDAP;

    @Autowired
    private DataSourceConnector dataSourceConnector;

    @Value("${oidc.issuer}")
    private String issuer;

    @Value("${oidc.secret}")
    private String secret;

    @Value("${oidc.id}")
    private String id;

    @GET
    @PreAuthorize("hasAuthority('ROLE_USER')")
    @Path("/getRefreshToken")
    public Response getRefreshToken(){
        OIDCAuthenticationToken authentication = (OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        return Response.status(200).entity(authentication.getRefreshTokenValue()).build();
    }

    @GET
    @PreAuthorize("hasAuthority('ROLE_USER')")
    @Path("/getJWTToken")
    public Response getAccessToken(){
        OIDCAuthenticationToken authentication = (OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        return Response.status(200).entity(authentication.getAccessTokenValue()).build();
    }

    @GET
    @Path("/getAccessToken")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAccessTokenFromRefreshToken(@Context final HttpServletRequest request,
                                                   @QueryParam("refreshToken") String refreshToken){

        if (refreshToken == null || refreshToken.isEmpty()) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(String.format(errorMessage, 400, "Bad Request", "Missing refreshToken parameter"))
                    .type(MediaType.APPLICATION_JSON).build();
        }

        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httppost = new HttpPost(issuer+"/token");

        // Request parameters and other properties.
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("client_id", id));
        params.add(new BasicNameValuePair("client_secret", secret));
        params.add(new BasicNameValuePair("grant_type", "refresh_token"));
        params.add(new BasicNameValuePair("refresh_token", refreshToken));
        params.add(new BasicNameValuePair("scope", "openid"));

        HttpResponse response = null;

        try {
            httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
            //Execute and get the response.
            response = httpclient.execute(httppost);
            org.apache.http.HttpEntity entity = response.getEntity();

            if (response.getStatusLine().getStatusCode() == 401) {
                return Response.status(Response.Status.UNAUTHORIZED)
                        .entity(String.format(errorMessage, 401, "Unauthorised", "Invalid refreshToken token " + refreshToken))
                        .type(MediaType.APPLICATION_JSON).build();
            }

            String serverMessage = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8.name());
            return Response.status(response.getStatusLine().getStatusCode())
                    .entity(serverMessage).type(MediaType.APPLICATION_JSON).build();

        }  catch (UnsupportedEncodingException uee) {
            logger.error(uee);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(String.format(errorMessage, 500, "Fail to get access token.", uee.getMessage()))
                    .type(MediaType.APPLICATION_JSON).build();

        } catch (IOException ioe) {
            logger.error(ioe);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(String.format(errorMessage, 500, "Fail to get access token.", ioe.getMessage()))
                    .type(MediaType.APPLICATION_JSON).build();

        }
    }

    @GET
    @Path("/getToken")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getToken(@QueryParam("accessToken") String accessToken){
        logger.debug("Refresh token " + accessToken);
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httppost = new HttpPost(issuer+"/token");

        // Request parameters and other properties.
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("client_id", id));
        params.add(new BasicNameValuePair("client_secret", secret));
        params.add(new BasicNameValuePair("grant_type", "refresh_token"));
        params.add(new BasicNameValuePair("refresh_token", accessToken));
        params.add(new BasicNameValuePair("scope", "openid"));
        try {
            httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
            //Execute and get the response.
            HttpResponse response = null;
            response = httpclient.execute(httppost);
            org.apache.http.HttpEntity entity = response.getEntity();
            if (entity != null) {
                try (InputStream instream = entity.getContent()) {
                    logger.debug(IOUtils.toString(instream, StandardCharsets.UTF_8.name()));
                }
            }

        }  catch (UnsupportedEncodingException uee) {
            logger.error(uee);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(String.format(errorMessage, 500, "Fail to get access token.", uee.getMessage()))
                    .type(MediaType.APPLICATION_JSON).build();

        } catch (IOException ioe) {
            logger.error(ioe);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(String.format(errorMessage, 500, "Fail to get access token.", ioe.getMessage()))
                    .type(MediaType.APPLICATION_JSON).build();

        }

        return Response.status(200).type(MediaType.APPLICATION_JSON).build();
    }

    @GET
    @Path("/getUserInfo")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUserInfo(@QueryParam("accessToken") String accessToken) throws JsonProcessingException {
        //return Response.status(404).entity(compose404Message("This is a test message.")).type(MediaType.APPLICATION_JSON).build();
        // call aai with accessToken
        logger.info(accessToken);
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            protected boolean hasError(HttpStatus statusCode) {
                return false;
            }});
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization","Bearer " + accessToken);
        HttpEntity<String>  request = new HttpEntity<>(headers);
        //logger.info(restTemplate.exchange(fooResourceUrl, HttpMethod.GET, request, Object.class));
        ResponseEntity<String> response = restTemplate.exchange(issuer +"userinfo", HttpMethod.GET, request, String.class);
        if(response.getStatusCode() == HttpStatus.OK) {
            JsonObject userInfo = new JsonParser().parse(response.getBody()).getAsJsonObject();
            JsonArray roles = new JsonArray();
            AuthoritiesMapper.map(userInfo.get("edu_person_entitlements").getAsJsonArray()).forEach(grantedAuthority -> {
                roles.add(grantedAuthority.getAuthority());
            });
            userInfo.add("roles", roles);
            return Response.status(response.getStatusCode().value()).entity(userInfo.toString()).type(MediaType.APPLICATION_JSON).build();
        } else {
            return Response.status(response.getStatusCode().value()).entity(response.getBody()).type(MediaType.APPLICATION_JSON).build();
        }

    }

    @GET
    @Path("/getUser")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUser()  {
        JsonObject userInfoJson = new JsonObject();
        try {
            OIDCAuthenticationToken authentication = null;
            try {
                authentication = (OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            }catch (Exception e){
                logger.info("Try to get User info - no context found ");
                return Response.status(404).entity(compose404Message("Get User info: no context found ")).type(MediaType.APPLICATION_JSON).build();
            }
            UserInfo userInfo = authentication.getUserInfo();
            if(userInfo == null){
                return Response.status(404).entity(compose404Message("Get User info: user info is null ")).type(MediaType.APPLICATION_JSON).build();
            }
            logger.info("Get User:\n name: " + authentication.getUserInfo().getGivenName() + " " + authentication.getUserInfo().getFamilyName());


            userInfoJson.addProperty("sub", userInfo.getSub());
            userInfoJson.addProperty("name", userInfo.getName());
            userInfoJson.addProperty("given_name", userInfo.getGivenName());
            userInfoJson.addProperty("family_name", userInfo.getFamilyName());
            userInfoJson.addProperty("email", userInfo.getEmail());
            JsonArray roles = new JsonArray();
            authentication.getAuthorities().forEach(grantedAuthority -> {
                roles.add(grantedAuthority.getAuthority());
            });
            userInfoJson.add("roles", roles);
        }catch (Exception e){
            logger.error("Get User info: An error occurred ",e);
            return Response.status(500).entity(compose500Message("Get User info: An error occurred ",e)).type(MediaType.APPLICATION_JSON).build();
        }
        return Response.status(200).entity(userInfoJson.toString()).type(MediaType.APPLICATION_JSON).build();
    }

/*
    @GET
    @Path("/katerina")
    @Produces(MediaType.APPLICATION_JSON)
    //@PreAuthorize("hasRole('ROLE_USER')")
    @PreAuthorize("hasAuthority('urn:geant:openaire.eu:group:Registered+User#aai.openaire.eu')")
    public Response getKaterina()  {
        return Response.status(200).build();
    }

    @GET
    @Path("/skata")
    @Produces(MediaType.APPLICATION_JSON)
    @PreAuthorize("hasRole('ROLE_USER')")
    public Response getKaterina2()  {
        return Response.status(200).build();
    }

    @GET
    @Path("/skata2")
    @Produces(MediaType.APPLICATION_JSON)
    @PreAuthorize("hasAuthority('skata')")
    public Response getKaterina3()  {
        return Response.status(200).build();
    }


    @GET
    @Path("/me")
    //@Produces(MediaType.APPLICATION_JSON)
    public Response getKaterina(@AuthenticationPrincipal UserDetails userDetails)  {
        //return Response.status(200).entity(userDetails).type(MediaType.APPLICATION_JSON).build();
        return Response.status(200).build();
    }
*/

    /* JSON Utility Methods */
    private String compose401Message(String message) {
        return  "{ \"status\" : \"error\", \"code\" : \"401\", \"message\" : \"  " + message +" \" }";
    }

    private String compose404Message(String message) {
        return  "{ \"status\" : \"error\", \"code\" : \"404\", \"message\" : \"  " + message +" \" }";
    }

    private String compose500Message(String message, Exception exception) {
        return  "{ \"status\" : \"fail\", \"code\" : \"500\", \"message\" : \"  " + message + "\", " +
                "\"description\" : \""+  exception.getMessage() +"\" }";
    }

    private String composeDataResponse(LDAPUser user) {
        return " { \"status\" : \"success\", \"code\": \"200\", " + "\"data\" : " + new Gson().toJson(user) + " }";
    }

    private String composeDataResponse(String fullname) {
        return " { \"status\" : \"success\", \"code\": \"200\", " + "\"data\" : " + new Gson().toJson(fullname) + " }";
    }
}
