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

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

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.json.JsonObject;
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.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.Certificate;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.ElectoralAuthority;
import com.scytl.products.ov.commons.beans.domain.model.certificate.CertificateEntity;
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.DuplicateEntryException;
import com.scytl.products.ov.commons.beans.exceptions.EntryPersistenceException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.infrastructure.remote.service.RemoteCertificateService;
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.ErrorCodes;
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.JsonUtils;
import com.scytl.products.ov.commons.verify.JSONVerifier;
import com.scytl.products.ov.vv.domain.common.SignedObject;
import com.scytl.products.ov.vv.domain.model.content.ElectoralAuthorityEntity;
import com.scytl.products.ov.vv.domain.model.content.ElectoralAuthorityRepository;
import com.scytl.products.ov.vv.domain.model.content.VerificationContentRepository;
import com.scytl.products.ov.vv.infrastructure.remote.VvRemoteCertificateService;

/**
 * Web service for handling electoral data resource.
 */
@Path(ElectoralDataResource.RESOURCE_PATH)
@Stateless(name = "vv-ElectoralDataResource")
public class ElectoralDataResource {
	
	static final String RESOURCE_PATH = "/electoraldata";
	
	static final String SAVE_ELECTORAL_DATA_PATH = "/tenant/{tenantId}/electionevent/{electionEventId}/electoralauthority/{electoralAuthorityId}/adminboard/{adminBoardId}";

    public static final String ADMINISTRATION_BOARD_CN_PREFIX = "AdministrationBoard ";

    // The name of the resource handle by this web service.
    private static final String RESOURCE_NAME = "electoraldata";

    // The name of the query parameter tenantId
    static final String QUERY_PARAMETER_TENANT_ID = "tenantId";

    // The name of the query parameter electionEventId
    static final String QUERY_PARAMETER_ELECTION_EVENT_ID = "electionEventId";

    // The name of the query parameter electoralAuthorityId
    static final String QUERY_PARAMETER_ELECTORAL_AUTHORITY_ID = "electoralAuthorityId";

    private static final String CONSTANT_ELECTORAL_AUTHORITY = "electoralAuthority";

    static final String QUERY_PARAMETER_ADMIN_BOARD_ID = "adminBoardId";

    // An instance of the verification content repository
    @EJB
    private VerificationContentRepository verificationContentRepository;

    // An instance of the electoral authority repository
    @EJB
    private ElectoralAuthorityRepository electoralAuthorityRepository;

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

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

    @Inject
    @VvRemoteCertificateService
    private RemoteCertificateService remoteCertificateService;

    // The track id instance
    @Inject
    private TrackIdInstance trackIdInstance;

    /**
     * Save electoral data given the tenant, the election event id and the electoral authority identifier.
     *
     * @param tenantId - the tenant identifier.
     * @param electionEventId - the election event identifier.
     * @param electoralAuthorityId - the electoral authority identifier.
     * @param request - the http servlet request.
     * @return Returns status 200 on success.
     * @throws ApplicationException if the input parameters are not valid.
     * @throws DuplicateEntryException if the entry already exists * @throws EntryPersistenceException if there is an error
     *         persisting the entry
     */
    @POST
    @Path(SAVE_ELECTORAL_DATA_PATH)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response saveElectoralData(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
                                      @PathParam(QUERY_PARAMETER_TENANT_ID) String tenantId,
                                      @PathParam(QUERY_PARAMETER_ELECTION_EVENT_ID) String electionEventId,
                                      @PathParam(QUERY_PARAMETER_ELECTORAL_AUTHORITY_ID) String electoralAuthorityId,
                                      @PathParam(QUERY_PARAMETER_ADMIN_BOARD_ID) String adminBoardId,
                                      @NotNull String inputJson,
                                      @Context HttpServletRequest request)
        throws ApplicationException, DuplicateEntryException, ResourceNotFoundException, EntryPersistenceException, IOException {

        trackIdInstance.setTrackId(trackingId);

        if (inputJson == null || inputJson.isEmpty()) {
            LOG.error("Missing request body");
            return Response.status(Response.Status.BAD_REQUEST).build();
        }
        // transaction id generation
        transactionInfoProvider.generate(tenantId, httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));

        LOG.info("Saving electoral authority information with id: {}, electionEventId: {}, and tenantId: {}.",
            electoralAuthorityId, electionEventId, tenantId);

        // validate parameters
        validateParameters(tenantId, electionEventId, electoralAuthorityId);

        LOG.info("Fetching the administration board certificate");
        String adminBoardCommonName = ADMINISTRATION_BOARD_CN_PREFIX + adminBoardId;

        CertificateEntity adminBoardCertificateEntity =
            remoteCertificateService.getAdminBoardCertificate(adminBoardCommonName);
        if (adminBoardCertificateEntity == null) {
            LOG.error(String.format("Could not find Admin Board certificate '%s'", adminBoardCommonName));
            return Response.status(Response.Status.PRECONDITION_FAILED).build();
        }

        String adminBoardCertPEM = adminBoardCertificateEntity.getCertificateContent();
        PublicKey adminBoardPublicKey;
        try {
            Certificate adminBoardCert = PemUtils.certificateFromPem(adminBoardCertPEM);
            adminBoardPublicKey = adminBoardCert.getPublicKey();
        } catch (GeneralCryptoLibException e) {
            LOG.error("An error occurred while loading the administration board certificate", e);
            return Response.status(Response.Status.PRECONDITION_FAILED).build();
        }

        ElectoralAuthorityEntity electoralAuthority = new ElectoralAuthorityEntity();
        electoralAuthority.setElectionEventId(electionEventId);
        electoralAuthority.setTenantId(tenantId);
        electoralAuthority.setElectoralAuthorityId(electoralAuthorityId);

        String signatureElectoralAuthority;
        try {
            JsonObject inputJsonObject = JsonUtils.getJsonObject(inputJson);
            String signedElectoralAuthorityJSON = inputJsonObject.getJsonObject(CONSTANT_ELECTORAL_AUTHORITY).toString();
            SignedObject signedElectoralAuthorityObject = ObjectMappers.fromJson(signedElectoralAuthorityJSON, SignedObject.class);
            signatureElectoralAuthority = signedElectoralAuthorityObject.getSignature();
        } catch (IOException e) {
            LOG.error("Error reading Electoral authority information", e);
            return Response.status(Response.Status.PRECONDITION_FAILED).build();
        }

        ElectoralAuthority electoralAuthorityObj;
        try {
            JSONVerifier verifier = new JSONVerifier();
            LOG.info("Verifying electoral authority signature");
            electoralAuthorityObj =
                verifier.verify(adminBoardPublicKey, signatureElectoralAuthority, ElectoralAuthority.class);
            LOG.info("Electoral authority signature was successfully verified");
        } catch (Exception e) {
            LOG.error("Electoral authority signature could not be verified", e);
            return Response.status(Response.Status.PRECONDITION_FAILED).build();
        }

        try {
            String electoralAuthorityJSON = ObjectMappers.toJson(electoralAuthorityObj);
            electoralAuthority.setJson(electoralAuthorityJSON);
            electoralAuthorityRepository.save(electoralAuthority);

            LOG.info("Electoral authority information with id: {}, electionEventId: {}, and tenantId: {} saved.",
                electoralAuthorityId, electionEventId, tenantId);

        } catch (DuplicateEntryException ex) {
            LOG.warn(
                "Duplicate entry tried to be inserted for electoral authority: {}, electionEventId: {}, and tenantId: {}.",
                electoralAuthorityId, electionEventId, tenantId, ex);
        }
        return Response.ok().build();
    }

    // Validate parameters.
    private void validateParameters(String tenantId, String electionEventId, String electoralAuthorityId)
        throws ApplicationException {
        if (tenantId == null || tenantId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_TENANT_ID);
        }

        if (electionEventId == null || electionEventId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_ELECTION_EVENT_ID);
        }

        if (electoralAuthorityId == null || electoralAuthorityId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_ELECTORAL_AUTHORITY_ID);
        }
    }
}
