/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.au.ui.ws.rs.application.operation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.security.cert.CertificateException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.au.services.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.au.services.domain.service.validation.AuthenticationTokenValidationService;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationExceptionMessages;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.exceptions.SemanticErrorException;
import com.scytl.products.ov.commons.beans.exceptions.SyntaxErrorException;
import com.scytl.products.ov.commons.beans.exceptions.ValidationException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.ui.Constants;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.HttpRequestService;
import com.scytl.products.ov.commons.util.ValidationUtils;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.commons.validation.ValidationResult;
import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;

/**
 * The end point for validation authentication token resource.
 */
@Path("/validations")
@Stateless
public class ValidationAuthenticationTokenResource {

    // The name of the parameter value tenant id.
    private static final String PARAMETER_VALUE_TENANT_ID = "tenantId";

    // The name of the parameter value voting card id.
    private static final String PARAMETER_VALUE_VOTING_CARD_ID = "votingCardId";

    // The name of the parameter value election event id.
    private static final String PARAMETER_VALUE_ELECTION_EVENT_ID = "electionEventId";

    // The name of the parameter value authentication token.
    private static final String PARAMETER_AUTHENTICATION_TOKEN = "authenticationToken";

	private static final String WHITESPACE_REGEXP = "\\s+";

    // The track id instance
    @Inject
    private TrackIdInstance trackIdInstance;
    // An instance of the authentication token validation service
    @Inject
    private AuthenticationTokenValidationService authenticationTokenValidationService;
    @Inject
    private TransactionInfoProvider transactionInfoProvider;
    @Inject
    private HttpRequestService httpRequestService;
   
    // The error logger.
    private static final Logger LOG = LoggerFactory.getLogger("std");

    /**
     * Returns a AuthenticationTokenValidationResult object with the result of the authentication token validation.
     *
     * @param tenantId - the tenant identifier.
     * @param electionEventId - the election event identifier.
     * @param votingCardId - the voting card identifier.
     * @param tokenString - the authentication token in json format received in the header.
     * @param request - the http servlet request.
     * @return If the operation is successfully performed, returns a response with HTTP status code 200 and the
     *         AuthenticationTokenValidationResult in json format.
     * @throws ApplicationException if one of the input parameters is not valid.
     * @throws ResourceNotFoundException the resource not found exception
     * @throws IOException if the authenticationToken is invalid and can not be mapped correctly to an object.
     * @throws SemanticErrorException if the authenticationToken is not valid due to semantic errors.
     * @throws SyntaxErrorException if the authenticationToken is not valid due to syntax errors.
     */
    @Path("/tenant/{tenantId}/electionevent/{electionEventId}/votingcard/{votingCardId}")
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response validateAuthenticationToken(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
                                                @PathParam(PARAMETER_VALUE_TENANT_ID) String tenantId,
                                                @PathParam(PARAMETER_VALUE_ELECTION_EVENT_ID) String electionEventId,
                                                @PathParam(PARAMETER_VALUE_VOTING_CARD_ID) String votingCardId,
                                                @HeaderParam(PARAMETER_AUTHENTICATION_TOKEN) @NotNull String tokenString,
                                                @Context HttpServletRequest request)
        throws ApplicationException, ResourceNotFoundException, IOException, CertificateException {
        // set the track id to be logged
        trackIdInstance.setTrackId(trackingId);

        // transaction id generation
        transactionInfoProvider.generate(tenantId, httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));

        // in case something from the input is not valid, it throws an exception
        validateInput(tenantId, electionEventId, votingCardId, tokenString);

        AuthenticationToken authenticationToken =
            ObjectMappers.fromJson(tokenString, AuthenticationToken.class);

        Response response;
        ValidationResult validationResult;
        try {
            // validate input authenticationToken for syntax or semantic errors
            ValidationUtils.validate(authenticationToken);
            //validate auth token data
            validationResult = authenticationTokenValidationService.validate(tenantId, electionEventId,
                votingCardId, authenticationToken);

            //we should NOT return 200 OK if the validation fails!
            if (validationResult.isResult()) {
                response = Response.ok(validationResult).build();
            } else {
                //not sure if the best status...
                response = Response.status(PRECONDITION_FAILED).entity(validationResult).build();
            }
        } catch (ValidationException e) {
            final String error = "Invalid authentication token format.";
            LOG.error(error, e);

            validationResult = new ValidationResult(false);
            ValidationError validationError = new ValidationError(ValidationErrorType.FAILED);
            //we could add the error message from the Exception but it may 'leak' too much information
            validationError.setErrorArgs(new String[]{error});
            validationResult.setValidationError(validationError);
            //not sure if the best status code..
            response = Response.status(PRECONDITION_FAILED).entity(validationResult).build();
        }

        return response;
    }

    // Does a basic validation of the input. In case something is wrong, just throws an exception.
    private void validateInput(String tenantId, String electionEventId, String votingCardId, String tokenString) throws ApplicationException, JsonParseException, JsonMappingException, IOException {
        if (tenantId == null || tenantId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_TENANT_ID_IS_NULL);
        }
        if (electionEventId == null || electionEventId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_ELECTION_EVENT_ID_IS_NULL);
        }
        if (votingCardId == null || votingCardId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_VOTING_CARD_ID_IS_NULL);
        }
        
        AuthenticationToken authenticationToken = ObjectMappers.fromJson(tokenString, AuthenticationToken.class);
        String serialized = ObjectMappers.toJson(authenticationToken);
		String strippedOriginalToken = tokenString.replaceAll(WHITESPACE_REGEXP,"");
        String strippedSerializedToken = serialized.replaceAll(WHITESPACE_REGEXP,"");
        
        if (strippedOriginalToken.length() != strippedSerializedToken.length()){
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_AUTH_TOKEN_INVALID_FORMAT);
        }
    }
}
