/*
 * 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.ei.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.json.JsonObjectBuilder;
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.products.ov.commons.util.JsonUtils;
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.ei.services.domain.common.SignedObject;
import com.scytl.products.ov.ei.services.domain.model.content.ElectoralAuthorityEntity;
import com.scytl.products.ov.ei.services.domain.model.content.ElectoralAuthorityRepository;
import com.scytl.products.ov.ei.services.infrastructure.remote.EiRemoteCertificateService;

/**
 * Web service for handling electoral data resource.
 */
@Path("/electoraldata")
@Stateless(name = "ei-ElectoralDataResource")
public class ElectoralDataResource {

    public static final String ADMINISTRATION_BOARD_CN_PREFIX = "AdministrationBoard ";

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

    private static final String CONSTANT_ELECTORAL_AUTHORITY = "electoralAuthority";

    private static final String CONSTANT_SIGNATURE = "signature";

    private static final String QUERY_PARAMETER_ADMIN_BOARD_ID = "adminBoardId";

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

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

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

    // The name of the query parameter ballotBoxId
    private static final String QUERY_PARAMETER_BALLOT_BOX_ID = "ballotBoxId";

    // 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
    @EiRemoteCertificateService
    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("tenant/{tenantId}/electionevent/{electionEventId}/electoralauthority/{electoralAuthorityId}/adminboard/{adminBoardId}")
    @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 {

        // set the track id to be logged
        trackIdInstance.setTrackId(trackingId);

        // 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);
        String adminBoardCertPEM = adminBoardCertificateEntity.getCertificateContent();
        Certificate adminBoardCert;
        try {
            adminBoardCert = PemUtils.certificateFromPem(adminBoardCertPEM);
        } catch (GeneralCryptoLibException e) {
            LOG.error("An error occurred while fetching the administration board certificate", e);
            return Response.status(Response.Status.PRECONDITION_FAILED).build();
        }
        PublicKey adminBoardPublicKey = adminBoardCert.getPublicKey();

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

        JsonObject inputJsonObject = JsonUtils.getJsonObject(inputJson);
        String signedElectoralAuthorityJSON = inputJsonObject.getJsonObject(CONSTANT_ELECTORAL_AUTHORITY).toString();
        SignedObject signedElectoralAuthorityObject = ObjectMappers.fromJson(signedElectoralAuthorityJSON, SignedObject.class);
        String signatureElectoralAuthority = signedElectoralAuthorityObject.getSignature();

        JSONVerifier verifier = new JSONVerifier();
        ElectoralAuthority electoralAuthorityObj;
        try {
            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 {

            JsonObjectBuilder electoralAuthorityJSONBuilder = JsonUtils.jsonObjectToBuilder(JsonUtils.getJsonObject(ObjectMappers.toJson(electoralAuthorityObj)));
            electoralAuthorityJSONBuilder.add(CONSTANT_SIGNATURE, signatureElectoralAuthority);
            String electoralAuthorityJSON = electoralAuthorityJSONBuilder.build().toString();
            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 ballotBoxId)
        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 (ballotBoxId == null || ballotBoxId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_BALLOT_BOX_ID);
        }
    }
}
