package eu.dnetlib.openaire.usermanagement;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import eu.dnetlib.openaire.user.pojos.RegisteredService;
import eu.dnetlib.openaire.usermanagement.utils.RegisteredServicesUtils;
import eu.dnetlib.openaire.usermanagement.utils.TokenUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.http.HttpResponse;
import org.apache.log4j.Logger;
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.method.P;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;


public class RegisterServiceServlet extends HttpServlet {

    private Logger logger = Logger.getLogger(RegisterServiceServlet.class);

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
                config.getServletContext());
    }

    @Autowired
    private RegisteredServicesUtils registeredServicesUtils;

    @Autowired
    private TokenUtils tokenUtils;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        OIDCAuthenticationToken authentication = (OIDCAuthenticationToken) SecurityContextHolder.
                getContext().getAuthentication();
        String userid = authentication.getSub();

        StringBuilder name = new StringBuilder().append(authentication.getUserInfo().getGivenName().charAt(0));
        name.append(authentication.getUserInfo().getFamilyName().charAt(0));
        request.getSession().setAttribute("name", name.toString());

        String idParam = request.getParameter("id");

        if (idParam != null && !idParam.isEmpty()) { // EDIT CASE
            //System.out.println("In edit");
            try {
                int id = Integer.parseInt(idParam);
                RegisteredService registeredService = registeredServicesUtils.getRegisteredServiceDao().fetchRegisteredServiceById(id);

                if (registeredService != null && registeredServicesUtils.isAuthorized(userid, id)) {
                    ServiceResponse serviceResponse = tokenUtils.getRegisteredService(registeredService.getClientId(), registeredService.getRegistrationAccessToken());

                    updateFormFields(request, registeredService.getName(), registeredService.getKeyType(), serviceResponse);

                } else {
                    if (registeredService == null) {
                        //System.out.println("No service found!");
                        request.getSession().setAttribute("message", "Not valid registered service with given id " + id + ".");
                        response.sendRedirect("./registeredServices");
                        logger.warn("Not valid registered service with " + id + "id.");

                    } else {
                        //System.out.println("Not authorized");
                        request.getSession().setAttribute("message", "Not authorized to edit the registered service with id " + id + ".");
                        response.sendRedirect("./registeredServices");
                        logger.warn("Not authorized to edit the service with " + id + "id.");
                    }
                }

            } catch (NumberFormatException nfe) {
                //System.out.println("WRONG FORMAT");
                request.getSession().setAttribute("message", "Invalid service id.");
                response.sendRedirect("./registeredServices");
                logger.error("Invalid service id.", nfe);

            } catch (SQLException sqle) {
                //System.out.println("SQL PROBLEM");
                request.getSession().setAttribute("message", "Could not fetch registered service.");
                response.sendRedirect("./registeredServices");
                logger.error("Could not fetch registered service.", sqle);
            }

        } else {// NEW SERVICE CASE
            //Careful! Redirects in method
            request.getSession().setAttribute("first_name", null);
            request.getSession().setAttribute("key_type", null);
            request.getSession().setAttribute("jwksUri", null);
            request.getSession().setAttribute("value", null);
            checkNumberOfRegisteredServices(request, response, authentication);
        }

        response.setContentType("text/html");
        request.getRequestDispatcher("./registerService.jsp").include(request, response);
    }

    private void updateFormFields(HttpServletRequest request, String serviceName, String keyType, ServiceResponse serviceResponse) {
        //System.out.println("UPDATING FORM");
        request.getSession().setAttribute("first_name", serviceName);
        //System.out.println("Service response URI " + serviceResponse.getJwksUri());
        request.getSession().setAttribute("key_type", keyType);
        if (keyType != null) {
            if (keyType.equals("uri")) {
                request.getSession().setAttribute("jwksUri", serviceResponse.getJwksUri());
            } else {
                Key key;
                if (serviceResponse.getJwks() != null) {
                    key = serviceResponse.getJwks().keys[0];
                } else {
                    key = new Key();
                }
                //System.out.println("Service response keys " + serviceResponse.getJwksUri());
                Gson gson = new GsonBuilder().setPrettyPrinting().create();
                request.getSession().setAttribute("value", gson.toJson(key));
            }
        }
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        OIDCAuthenticationToken authentication = (OIDCAuthenticationToken) SecurityContextHolder.
                getContext().getAuthentication();

        response.setContentType("text/html");
        boolean canProceed = true;

        String mode = request.getParameter("mode").trim();
        //System.out.println("Mode was " + mode);
        checkmode(mode);
        //System.out.println("Mode is " + mode);

        String serviceId = request.getParameter("id");

        String name = request.getParameter("first_name").trim();
        if (name.isEmpty()) {
            request.getSession().setAttribute("first_name_error", true);
            canProceed = false;
        }
        String keyType = request.getParameter("key_type");
        String jwksUri = null;
        String jwksString = null;
        Jwks jwks = null;
        if(keyType != null) {
            keyType = keyType.trim();
            if (keyType.equals("uri")) {
                jwksUri = request.getParameter("uri");
                request.getSession().setAttribute("jwksUri", jwksUri);

                String[] schemes = {"https"};
                UrlValidator urlValidator = new UrlValidator(schemes);
                if (!urlValidator.isValid(jwksUri)) {
                    request.getSession().setAttribute("uri_error", true);
                    canProceed = false;
                }
            } else {
                jwksString = request.getParameter("value");
                try {
                    Gson gson = new GsonBuilder().registerTypeAdapter(Jwks.class, new JwksDeserializer()).create();
                    String jwksSet = String.format("{\"keys\":[%s]}", jwksString);
                    jwks = gson.fromJson(jwksSet, Jwks.class);
                    request.getSession().setAttribute("value", jwksString);

                    if (jwks.getKeys() == null || jwks.getKeys().length == 0) {
                        //System.out.println("Something wrong with the keys.");
                        request.getSession().setAttribute("value_error", true);
                        canProceed = false;
                    }


                } catch (JsonParseException jpe) {
                    request.getSession().setAttribute("value_error", true);
                    canProceed = false;
                }
            }
        }
        String userid = authentication.getSub();
        String email = authentication.getUserInfo().getEmail();
        ServiceResponse serviceResponse = null;

        if (nameIsValid(name) && userInfoIsValid(userid, email) && keyIsValid(keyType, jwksUri, jwksString) && canProceed) {

            String serverMessage;

            if (mode.equals("create")) {
                //Careful! Redirects in method
                if (!checkNumberOfRegisteredServices(request, response, authentication)) {
                    return;
                }
                String serverRequestJSON = null;
                if(keyType == null) {
                    serverRequestJSON = createServiceJson(null, name, email);
                } else if (keyType.equals("uri")) {
                    serverRequestJSON = createServiceJson(null, name, email, jwksUri);
                } else if (keyType.equals("value")){
                    serverRequestJSON = createServiceJson(null, name, email, jwks);
                }
                if(serverRequestJSON != null) {
                    //System.out.println("SERVER JSON " + serverRequestJSON);
                    serverMessage = tokenUtils.registerService(serverRequestJSON);
                    logger.debug(serverMessage);
                    if (serverMessage == null) {
                        request.getSession().setAttribute("message", "There was an error registering your service. Please try again later.");
                        response.sendRedirect("./registeredServices");
                        return;
                    }
                    serviceResponse = new Gson().fromJson(serverMessage, ServiceResponse.class);
                    String client_id = serviceResponse.getClientId();

                    RegisteredService registeredService = new RegisteredService(client_id, userid, name, serviceResponse.getRegistrationAccessToken(), keyType);

                    try {
                        registeredServicesUtils.addRegistedService(registeredService);
                        if(registeredService.getKeyType() != null) {
                            request.getSession().setAttribute("success",
                                    "Your service has been successfully registered!<br>" +
                                            "<b>Client ID</b>: " + serviceResponse.getClientId());
                        } else {
                            request.getSession().setAttribute("success",
                                    "Your service has been successfully registered!<br>" +
                                          "<b>Client ID</b>: " + serviceResponse.getClientId() +
                                          "<br><span style=\"word-wrap: break-word\"><b>Client Secret</b>:" + serviceResponse.getClientSecret() + "</span>");
                        }

                    } catch (SQLException sqle) {
                        logger.error("Fail to save service.", sqle);
                        request.getSession().setAttribute("message", "There was an error registering your service. Please try again later.");
                        response.sendRedirect("./registeredServices");
                        return;
                    }
                } else {
                    logger.error("Service request JSON is null");
                    request.getSession().setAttribute("message", "There was an error registering your service. Please try again later.");
                    response.sendRedirect("./registeredServices");
                    return;
                }
            } else {
                int serviceIdInt = 0;
                if (serviceId == null || serviceId.isEmpty()) { //TODO WRONG MESSAGE
                    request.getSession().setAttribute("message", "Service with id " + serviceId + " does not exist.");
                    response.sendRedirect("./registeredServices");

                } else {
                    //System.out.println("In edit...");
                    try {
                        serviceIdInt = Integer.parseInt(serviceId);
                        if (!registeredServicesUtils.isAuthorized(authentication.getSub(), serviceIdInt)) {
                            request.getSession().setAttribute("message", "You have no permission to edit the service.");
                            response.sendRedirect("./registeredServices");

                        } else {

                            RegisteredService registeredService = registeredServicesUtils.getRegisteredServiceDao().fetchRegisteredServiceById(serviceIdInt);
                            if (registeredService != null && registeredService.getClientId() != null) {
                                String serverRequestJSON = null;
                                if (keyType == null) {
                                    serverRequestJSON = createServiceJson(registeredService.getClientId(), name, email);
                                } else if (keyType.equals("uri")) {
                                    serverRequestJSON = createServiceJson(registeredService.getClientId(), name, email, jwksUri);
                                } else if (keyType.equals("value")) {
                                    serverRequestJSON = createServiceJson(registeredService.getClientId(), name, email, jwks);
                                }
                                if (serverRequestJSON != null) {
                                    //System.out.println("SERVER JSON " + serverRequestJSON);
                                    HttpResponse resp = tokenUtils.updateService(registeredService.getClientId(), serverRequestJSON, registeredService.getRegistrationAccessToken());
                                    if (resp.getStatusLine().getStatusCode() == 200) {
                                        //System.out.println("NAME >>>>" + name);
                                        registeredService.setName(name);

                                        //System.out.println("Client Id " + registeredService.getClientId());
                                        try {
                                            registeredServicesUtils.getRegisteredServiceDao().update(registeredService);
                                        } catch (SQLException sqle) {
                                            logger.error("Unable to contact db.", sqle);
                                            request.getSession().setAttribute("message", "Fail to delete the service. Please try again later.");
                                            response.setContentType("text/html");
                                            request.getRequestDispatcher("./registeredServices.jsp").include(request, response);
                                            return;
                                        }
                                        request.getSession().setAttribute("success",
                                                "Your service has been successfully updated!<br>" +
                                                        "<b>Client ID</b>: " + registeredService.getClientId());
                                    }

                                } else {
                                    request.getSession().setAttribute("message", "Service with id " + serviceId + " does not exist.");
                                    response.sendRedirect("./registeredServices");
                                    return;
                                }
                            } else {
                                logger.error("Service request JSON is null");
                                request.getSession().setAttribute("message", "There was an error registering your service. Please try again later.");
                                response.sendRedirect("./registeredServices");
                                return;
                            }
                        }
                    } catch(SQLException sqle){
                        logger.error("Unable to access service with id " + serviceId, sqle);
                        request.getSession().setAttribute("message", "There was an error accessing your service.");
                        response.sendRedirect("./registeredServices");

                    } catch(NumberFormatException nfe){
                        logger.error("Unable to access service with id " + serviceId, nfe);
                        request.getSession().setAttribute("message", "Service with id " + serviceId + " does not exist.");
                        response.sendRedirect("./registeredServices");
                    }
                }
            }

        } else {
            //something is wrong with the form and the error messages will appear
            request.getSession().setAttribute("first_name", name);
            request.getSession().setAttribute("key_type", keyType);
            request.getSession().setAttribute("uri", jwksUri);
            request.getSession().setAttribute("value", jwksString);

            if (serviceId != null && !serviceId.isEmpty()) {
                request.getRequestDispatcher("./registerService.jsp?id=" + serviceId).forward(request, response);

            } else {
                request.getRequestDispatcher("./registerService.jsp").include(request, response);

            }
            return;
        }

        response.sendRedirect("./registeredServices");
    }

    private void checkmode(String mode) {
        if (mode != null && !mode.isEmpty()) {
            if (!mode.equals("edit") || mode.equals("create")) {
                mode = "create";
            }
        } else {
            mode = "create";
        }
    }

    private boolean keyIsValid(String keyType, String jwksUri, String jwksString) {
        return keyType == null || (keyType.equals("uri") && jwksUri != null && !jwksUri.isEmpty()) ||
                keyType.equals("value") && jwksString != null && !jwksString.isEmpty();
    }

    private boolean userInfoIsValid(String userid, String email) {
        return userid != null && !userid.isEmpty() &&
                email != null && !email.isEmpty();
    }

    private boolean nameIsValid(String name) {
        return name != null && !name.isEmpty();
    }

    private boolean checkNumberOfRegisteredServices(HttpServletRequest request, HttpServletResponse response, OIDCAuthenticationToken authentication) throws IOException {
        try {
            long numberOfRegisteredServices =
                    registeredServicesUtils.getRegisteredServiceDao().countRegisteredServices(authentication.getSub());

            if (numberOfRegisteredServices >= 5) {
                response.sendRedirect("./registeredServices"); // The message there already exists.
                return false;
            }

        } catch (SQLException sqle) {
            logger.error("Unable to count registered services.", sqle);
            request.getSession().setAttribute("message", "Unable to contact DB. Please try again later.");
            response.sendRedirect("./registeredServices");
            return false;
        }

        return true;
    }

    private static String createServiceJson(String clientId, String name, String email) {
        ServiceRequest serviceJSON = new ServiceRequest();
        serviceJSON.setClientId(clientId);
        serviceJSON.setClientName(name);
        serviceJSON.setContacts(new String[]{email});
        serviceJSON.setToken_endpoint_auth_method("client_secret_basic");
        serviceJSON.setTokenEndpointAuthSigningAlg(null);
        GsonBuilder builder = new GsonBuilder();
        builder.serializeNulls();
        Gson gson = builder.create();
        //System.out.println("Created json " + serviceJSON);
        return gson.toJson(serviceJSON);
    }

    private static String createServiceJson(String clientId, String name, String email, String jwksURI) {
        ServiceRequest serviceJSON = new ServiceRequest();
        serviceJSON.setClientId(clientId);
        serviceJSON.setClientName(name);
        serviceJSON.setContacts(new String[]{email});
        serviceJSON.setJwksUri(jwksURI);

        GsonBuilder builder = new GsonBuilder();
        builder.serializeNulls();
        Gson gson = builder.create();
        //System.out.println("Created json " + serviceJSON);
        return gson.toJson(serviceJSON);
    }

    private static String createServiceJson(String clientId, String name, String email, Jwks jwks) {
        ServiceRequest serviceJSON = new ServiceRequest();
        serviceJSON.setClientId(clientId);
        serviceJSON.setClientName(name);
        serviceJSON.setContacts(new String[]{email});
        serviceJSON.setJwks(jwks);

        GsonBuilder builder = new GsonBuilder();
        builder.serializeNulls();
        Gson gson = builder.create();
        //System.out.println("Created json " + serviceJSON);
        return gson.toJson(serviceJSON);
    }

}
