package be.ehealth.businessconnector.test.genericasync.helper;

import be.cin.mycarenet.esb.common.v2.OrigineType;
import be.cin.nip.async.generic.Confirm;
import be.cin.nip.async.generic.ConfirmResponse;
import be.cin.nip.async.generic.GetResponse;
import be.cin.nip.async.generic.MsgQuery;
import be.cin.nip.async.generic.MsgResponse;
import be.cin.nip.async.generic.Query;
import be.cin.nip.async.generic.Responses;
import be.cin.nip.async.generic.TAckResponse;
import be.ehealth.business.mycarenetdomaincommons.builders.BlobBuilderFactory;
import be.ehealth.business.mycarenetdomaincommons.builders.CommonBuilder;
import be.ehealth.business.mycarenetdomaincommons.builders.RequestBuilderFactory;
import be.ehealth.business.mycarenetdomaincommons.domain.Blob;
import be.ehealth.business.mycarenetdomaincommons.domain.McnPackageInfo;
import be.ehealth.business.mycarenetdomaincommons.mapper.DomainBlobMapper;
import be.ehealth.business.mycarenetdomaincommons.util.McnConfigUtil;
import be.ehealth.business.mycarenetdomaincommons.util.WsAddressingUtil;
import be.ehealth.businessconnector.genericasync.builders.BuilderFactory;
import be.ehealth.businessconnector.genericasync.domain.ProcessedMsgResponse;
import be.ehealth.businessconnector.genericasync.exception.GenAsyncBusinessConnectorException;
import be.ehealth.businessconnector.genericasync.mappers.CommonInputMapper;
import be.ehealth.businessconnector.genericasync.session.GenAsyncService;
import be.ehealth.businessconnector.genericasync.session.GenAsyncSessionServiceFactory;
import be.ehealth.businessconnector.test.testcommons.utils.FileTestUtils;
import be.ehealth.technicalconnector.exception.ConnectorException;
import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.handler.domain.WsAddressingHeader;
import be.ehealth.technicalconnector.service.keydepot.KeyDepotManager;
import be.ehealth.technicalconnector.service.keydepot.KeyDepotManagerFactory;
import be.ehealth.technicalconnector.utils.ConnectorXmlUtils;
import org.junit.Assert;
import org.mapstruct.factory.Mappers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import static be.ehealth.businessconnector.genericasync.domain.GenericAsyncConstants.CONFIRM_SOAP_ACTION;
import static be.ehealth.businessconnector.genericasync.domain.GenericAsyncConstants.GET_SOAP_ACTION;
import static org.junit.Assert.assertNotNull;

/**
 * @author EHP
 */
public class GetAndConfirmTestHelper {

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

    private String projectName;
    private String outputFileExtension;
    private String outputDir;
    private int maxMessages;
    private List<String> messageNames;
    private BusinessResponseValidator businessResponseValidator;
    private GenAsyncService service;
    private OrigineType origin;
    private boolean replyToEtk;


    private GetAndConfirmTestHelper(Builder builder) throws ConnectorException {
        projectName = builder.projectName;
        outputFileExtension = builder.outputFileExtension;
        outputDir = builder.outputDir;
        maxMessages = builder.maxMessages;
        messageNames = builder.messageNames;
        businessResponseValidator = builder.businessResponseValidator;
        service = GenAsyncSessionServiceFactory.getGenAsyncService(projectName);
        origin = givenOrigin();
        replyToEtk = builder.replyToEtk;
    }

    public static Builder Builder() {
        return new Builder();
    }


    public void checkIfTAckResponsesWithSameReferencesAreStillReturned(List<String> tAckResponseReferences) throws Exception {
        Assert.assertTrue("Add tAckResponse references you wish to check to the tAckResponseReferences list", tAckResponseReferences.size() > 0);
        
        final GetResponse messages = getMessages(origin, service);
        final Responses messageReturn = messages.getReturn();
        if (messageReturn.getTAckCount() != 0) {
            for (TAckResponse tackResp : messageReturn.getTAckResponses()) {
                String reference = tackResp.getTAck().getReference();
                if (tAckResponseReferences.contains(reference)) {
                    final String msg = "\tTackResponse found after it was already confirmed : reference: >" + reference + "< , appliesTo >" + tackResp.getTAck().getAppliesTo() + "< " + tackResp.getTAck().getResultMessage();
                    LOG.debug(msg);
                    Assert.fail(msg);
                }
            }
        }
    }

    public void checkIfMessagesWithSameReferencesAreStillReturned(List<String> msgResponseReferences) throws Exception {
        Assert.assertTrue("Add msgResponse references you wish to check to the msgResponseReferences list", msgResponseReferences.size() > 0);
        
        final GetResponse messages = getMessages(origin, service);
        final Responses messageReturn = messages.getReturn();
        if (messageReturn.getMsgCount() != 0) {
            for (MsgResponse msgResponse : messageReturn.getMsgResponses()) {
                String reference = msgResponse.getDetail().getReference();
                if (msgResponseReferences.contains(reference)) {
                    final String msg = "\tmessageResponse found even after its reception was already confirmed : reference: >" + msgResponse.getDetail().getReference() + "< , inputRef : " + msgResponse.getCommonOutput().getInputReference() + " outputRef: " + msgResponse.getCommonOutput().getOutputReference();
                    LOG.debug(msg);
                    Assert.fail(msg);
                }
            }
        }
    }
    
    private OrigineType givenOrigin() throws TechnicalConnectorException {
        McnPackageInfo packageInfo = McnConfigUtil.retrievePackageInfo("genericasync." + projectName);
        CommonBuilder commonBuilder = RequestBuilderFactory.getCommonBuilder(projectName);
        return Mappers.getMapper(CommonInputMapper.class).map(commonBuilder.createOrigin(packageInfo));
    }


    public GetResponse performGetAndConfirm(boolean confirmMessages) throws Exception {
        GetResponse getResponse = getMessages(origin, service);

        if (getResponse.getReturn().getMsgCount() == 0 && getResponse.getReturn().getTAckCount() == 0) {
            LOG.warn("NO MESSAGES TO CONFIRM ");
        } else if (confirmMessages) {
            ConfirmResponse confirmResponse = confirmTheseMessages(origin, service, getResponse);
            LOG.debug(ConnectorXmlUtils.toString(confirmResponse));
            assertNotNull(confirmResponse);
        }
        return getResponse;
    }

    private GetResponse getMessages(OrigineType origin, GenAsyncService service) throws Exception {
        MsgQuery msgQuery = new MsgQuery();
        msgQuery.setInclude(true);
        msgQuery.setMax(maxMessages);
        msgQuery.getMessageNames().addAll(messageNames);

        Query tackQuery = new Query();
        tackQuery.setInclude(true);
        tackQuery.setMax(maxMessages);

        WsAddressingHeader getResponseHeader = WsAddressingUtil.createHeader(null, GET_SOAP_ACTION);
        GetResponse getResponse = service.getRequest(BuilderFactory.getRequestObjectBuilder(projectName)
                .buildGetRequest(origin, msgQuery, tackQuery,
                        replyToEtk ? KeyDepotManagerFactory.getKeyDepotManager().getETK(KeyDepotManager.EncryptionTokenType.HOLDER_OF_KEY).getEncoded() : null),
                getResponseHeader);

        // validate the get responses ( including check on xades if present)
       BuilderFactory.getResponseObjectBuilder().handleGetResponse(getResponse);
        
        processGetResponse(getResponse);
        
        return getResponse;
    }
    

    private ConfirmResponse confirmTheseMessages(OrigineType origin, GenAsyncService service, GetResponse getResponse) throws URISyntaxException, TechnicalConnectorException, GenAsyncBusinessConnectorException, InstantiationException {
        WsAddressingHeader responseConfirmHeader = WsAddressingUtil.createHeader(null, CONFIRM_SOAP_ACTION);
        Confirm request = BuilderFactory.getRequestObjectBuilder(projectName).buildConfirmWithReferences(origin, getResponse);
        ConfirmResponse confirmResponse = service.confirmRequest(request, responseConfirmHeader);
        return confirmResponse;
    }

    private void processGetResponse(GetResponse getResponse) throws Exception {
        processMsgResponses(getResponse);
        processTAckResponses(getResponse);
        FileTestUtils.writeToFile(new String(ConnectorXmlUtils.toByteArray(getResponse)), "getResponse", outputFileExtension, outputDir);
        ConnectorXmlUtils.dump(getResponse);
    }

    private void processTAckResponses(GetResponse getResponse) throws IOException, TechnicalConnectorException, GenAsyncBusinessConnectorException {
        for (TAckResponse tackResponse : getResponse.getReturn().getTAckResponses()) {
            BuilderFactory.getResponseObjectBuilder().validateTAckXadesT(tackResponse, tackResponse.getXadesT().getValue(), projectName);

            String tack = ConnectorXmlUtils.toString(tackResponse.getTAck());
            
            // handle with your own logic
            FileTestUtils.writeToFile(tack, "tackResponseValue", outputFileExtension, outputDir);
            LOG.debug("received Tack response : " + tack);
        }
    }

    private void processMsgResponses(GetResponse getResponse) throws Exception {
        for (MsgResponse msgResponse : getResponse.getReturn().getMsgResponses()) {
            Blob mappedBlob = DomainBlobMapper.mapToBlob(msgResponse.getDetail());

            // No hash in message response
            mappedBlob.setHashTagRequired(false);

            byte[] unwrappedMessageByteArray = BlobBuilderFactory.getBlobBuilder(projectName).checkAndRetrieveContent(mappedBlob);

            if(msgResponse.getDetail().getContentEncryption() != null) {
                ProcessedMsgResponse<byte[]> processedGetResponse = BuilderFactory.getResponseObjectBuilder().processEncryptedResponse(msgResponse, projectName, byte[].class);
                unwrappedMessageByteArray = processedGetResponse.getBusinessResponse();
                validateBusinessResponse(processedGetResponse.getBusinessResponse());
            } else {
                validateBusinessResponse(unwrappedMessageByteArray);
            }

            // handle the business message with your own logic
            String businessResponse = new String(unwrappedMessageByteArray, "UTF-8");
            FileTestUtils.writeToFile(businessResponse, "getResponseBusinessMessage", outputFileExtension, outputDir);
            LOG.debug("received Business Message : " + businessResponse);
        }
    }

    private void validateBusinessResponse(byte[] unwrappedMessageByteArray) throws Exception {
        if (businessResponseValidator != null) {
            businessResponseValidator.validate(new String(unwrappedMessageByteArray, "UTF-8"));
        }
    }


    public static final class Builder {
        private String projectName;
        private String outputFileExtension;
        private String outputDir;
        private int maxMessages;
        private List<String> messageNames = new ArrayList<String>();
        private BusinessResponseValidator businessResponseValidator;
        private Class blobClass;
        private boolean replyToEtk;

        private Builder() {
        }

        public Builder projectName(String projectName) {
            this.projectName = projectName;
            return this;
        }

        public Builder outputFileExtension(String outputFileExtension) {
            this.outputFileExtension = outputFileExtension;
            return this;
        }

        public Builder outputDir(String outputDir) {
            this.outputDir = outputDir;
            return this;
        }

        public Builder maxMessages(int maxMessages) {
            this.maxMessages = maxMessages;
            return this;
        }

        public Builder messageNames(List<String> messageNames) {
            if (messageNames != null) {
                this.messageNames.addAll(messageNames);
            }    
            return this;
        }

        public Builder BusinessResponseValidator(BusinessResponseValidator businessResponseValidator) {
            this.businessResponseValidator = businessResponseValidator;
            return this;
        }

        public Builder blobClass(Class blobClass) {
            this.blobClass = blobClass;
            return this;
        }

        public Builder replyToEtk(boolean replyToEtk) {
            this.replyToEtk = replyToEtk;
            return this;
        }

        public GetAndConfirmTestHelper build() throws ConnectorException {
            return new GetAndConfirmTestHelper(this);
        }
    }

    public interface BusinessResponseValidator {
        void validate(String blobValue) throws Exception;
    }
}
