/*
 * 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.ea.ui.ws.rs.application.operation;

import java.io.IOException;

import javax.ejb.EJB;
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.PUT;
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 javax.ws.rs.core.Response.Status;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.commons.beans.domain.model.authentication.ExtendedAuthenticationUpdate;
import com.scytl.products.ov.commons.beans.domain.model.authentication.ExtendedAuthenticationUpdateRequest;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.AuthTokenValidationException;
import com.scytl.products.ov.commons.beans.exceptions.ExtendedAuthValidationException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
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.ea.domain.actions.ValidateExtendedAuthUpdateAction;
import com.scytl.products.ov.ea.domain.model.EncryptedSVK;
import com.scytl.products.ov.ea.domain.model.extendedauthentication.AllowedAttemptsExceededException;
import com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthenticationRepository;
import com.scytl.products.ov.ea.services.infrastructure.persistence.AuthenticationException;
import com.scytl.products.ov.ea.services.infrastructure.persistence.ExtendedAuthenticationService;
import com.scytl.products.ov.ea.services.infrastructure.persistence.ExtendedAuthenticationServiceImpl;

/**
 * Web service for querying for extended authentication.
 */
@Path(ExtendedAuthenticationResource.RESOURCE_PATH)
@Stateless
public class ExtendedAuthenticationResource {

    public static final String RESOURCE_PATH = "/extendedauthentication/authenticate";

    public static final String AUTHENTICATE = "/tenant/{tenantId}/electionevent/{electionevent}";

    public static final String UPDATE = "/tenant/{tenantId}/electionevent/{electionevent}";

    private static final String PARAMETER_VALUE_TENANT_ID = "tenantId";

    private static final String PARAMETER_VALUE_ELECTION_EVENT = "electionevent";

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

    @EJB
    protected ExtendedAuthenticationRepository extendedAuthenticationRepository;

    @Inject
    protected ExtendedAuthenticationService extendedAuthenticationService;

    private static final Logger LOG = LoggerFactory.getLogger(ExtendedAuthenticationResource.class);

    @Inject
    protected SecureLoggingWriter secureLoggerWriter;

    /** The track id instance. */
    @Inject
    protected TrackIdInstance trackIdInstance;

    @EJB
    protected ValidateExtendedAuthUpdateAction validateExtendedAuthUpdateAction;

    /**
     * The transaction info provider.
     */
    @Inject
    protected TransactionInfoProvider transactionInfoProvider;

    /**
     * The http request service.
     */
    @Inject
    protected HttpRequestService httpRequestService;

    @Path(ExtendedAuthenticationResource.AUTHENTICATE)
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response authenticate(@NotNull @PathParam(PARAMETER_VALUE_TENANT_ID) String tenantId,
            @NotNull @PathParam(PARAMETER_VALUE_ELECTION_EVENT) String electionEvent,
            @NotNull @HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackId,
            @NotNull ExtendedAuthentication extendedAuthentication, @Context HttpServletRequest request)
            throws GeneralCryptoLibException {

        ExtendedAuthResponse.Builder builder = new ExtendedAuthResponse.Builder();
        builder.setNumberOfRemainingAttempts(0);
        trackIdInstance.setTrackId(trackId);

        // transaction id generation
        transactionInfoProvider.generate(tenantId, httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));
        try {
            EncryptedSVK encryptedStartVotingKey = extendedAuthenticationService.authenticate(tenantId,
                extendedAuthentication.getAuthId(), extendedAuthentication.getExtraParam(), electionEvent);
            builder.setEncryptedSVK(encryptedStartVotingKey.getEncryptedSVK());
            builder.setResponseCode(Status.OK);

        } catch (ResourceNotFoundException e) {
			builder.setResponseCode(Status.NOT_FOUND);
			LOG.error("AuthId not found"
					+ createErrorDetails(tenantId, electionEvent, extendedAuthentication.getAuthId()), e);
		} catch (AllowedAttemptsExceededException e) {
			builder.setResponseCode(Status.FORBIDDEN);
			LOG.error("Allowed maximum number of (" + ExtendedAuthenticationServiceImpl.MAX_ALLOWED_NUMBER_OF_ATTEMPTS
					+ ") attempts exceeded"
					+ createErrorDetails(tenantId, electionEvent, extendedAuthentication.getAuthId()), e);
		} catch (AuthenticationException e) {
			if (e.getRemainingAttempts() == 0) {
				builder.setResponseCode(Status.FORBIDDEN);
			} else {
				builder.setResponseCode(Status.UNAUTHORIZED);
				builder.setNumberOfRemainingAttempts(e.getRemainingAttempts());
			}
			LOG.error("Extra parameter based authentication exception"
					+ createErrorDetails(tenantId, electionEvent, extendedAuthentication.getAuthId()), e);
		} catch (ApplicationException e) {
			builder.setResponseCode(Status.NOT_FOUND);
			LOG.error("AuthId error " + createErrorDetails(tenantId, electionEvent, extendedAuthentication.getAuthId()),
					e);
		}
        return Response.ok().entity(builder.build()).build();
    }

    private String createErrorDetails(String tenantId, String electionEvent, String authId) {
        StringBuilder sb = new StringBuilder();
        sb.append(" (Tenant Id: ");
        sb.append(tenantId);
        sb.append(", Election event: ");
        sb.append(electionEvent);
        sb.append(", Auth Id: ");
        sb.append(authId);
        sb.append(").");
        return sb.toString();
    }

    @Path(ExtendedAuthenticationResource.UPDATE)
    @PUT
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response update(@NotNull @PathParam(PARAMETER_VALUE_TENANT_ID) String tenantId,
            @NotNull @PathParam(PARAMETER_VALUE_ELECTION_EVENT) String electionEvent,
            @NotNull @HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackId,
            @NotNull @HeaderParam(PARAMETER_AUTHENTICATION_TOKEN) String authenticationToken,
            @NotNull ExtendedAuthenticationUpdateRequest extendedAuthenticationUpdateRequest,
            @Context HttpServletRequest request) {

        ExtendedAuthResponse.Builder builder = new ExtendedAuthResponse.Builder();
        String oldAuthID = "";
        try {
            trackIdInstance.setTrackId(trackId);

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

            final AuthenticationToken token = ObjectMappers.fromJson(authenticationToken, AuthenticationToken.class);

            ExtendedAuthenticationUpdate extendedAuthenticationUpdate = validateExtendedAuthUpdateAction.validate(tenantId, electionEvent, token,
                extendedAuthenticationUpdateRequest);

            oldAuthID = extendedAuthenticationUpdate.getOldAuthID();
			final com.scytl.products.ov.ea.domain.model.extendedauthentication.ExtendedAuthentication extendedAuthentication =
                extendedAuthenticationService.renewExistingExtendedAuthentication(tenantId,
                    oldAuthID, extendedAuthenticationUpdate.getNewAuthID(),
                    extendedAuthenticationUpdate.getNewSVK(), electionEvent);

            builder.setResponseCode(Status.OK);
            builder.setEncryptedSVK(extendedAuthentication.getEncryptedStartVotingKey());
            builder.setNumberOfRemainingAttempts(extendedAuthentication.getAttempts());

		} catch (ExtendedAuthValidationException e) {
			builder.setResponseCode(Status.BAD_REQUEST);
			LOG.error("Error validating the token + " + e.getErrorType().name(), e);
		} catch (AuthTokenValidationException e) {
			builder.setResponseCode(Status.BAD_REQUEST);
			LOG.error("Error validating the request + " + e.getErrorType().name(), e);
		} catch (ResourceNotFoundException e) {
			builder.setResponseCode(Status.NOT_FOUND);
			LOG.error("AuthId not found" + createErrorDetails(tenantId, electionEvent, oldAuthID), e);
		} catch (ApplicationException e) {
			builder.setResponseCode(Status.CONFLICT);
			LOG.error("AuthId error " + createErrorDetails(tenantId, electionEvent, oldAuthID), e);
		} catch (IOException e) {
			builder.setResponseCode(Status.CONFLICT);
			LOG.error("error parsing auth token error "
					+ createErrorDetails(tenantId, electionEvent, authenticationToken), e);
		}
        return Response.ok().entity(builder.build()).build();
    }
}
