package be.ehealth.businessconnector.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.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.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.utils.ConnectorXmlUtils;
import org.apache.commons.lang.Validate;
import org.junit.Assert;
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 int maxMessages;
    private List<String> messageNames;

    private GetAndConfirmTestHelper(Builder builder) {
        projectName = builder.projectName;
        outputFileExtension = builder.outputFileExtension;
        maxMessages = builder.maxMessages;
        messageNames = builder.messageNames;
    }

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


    public void checkIfTAckResponsesWithSameReferencesAreStillReturned(List<String> tAckResponseReferences) throws URISyntaxException, ConnectorException, InstantiationException, IOException {
        Validate.notEmpty(tAckResponseReferences, "Add tAckResponse references you wish to check to the tAckResponseReferences list");

        OrigineType origin = givenOrigin();
        GenAsyncService service = givenService();
        
        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 URISyntaxException, ConnectorException, InstantiationException, IOException {
        Validate.notEmpty(msgResponseReferences, "Add msgResponse references you wish to check to the msgResponseReferences list");

        OrigineType origin = givenOrigin();
        GenAsyncService service = givenService();
        
        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 GenAsyncService givenService() throws ConnectorException {
        return GenAsyncSessionServiceFactory.getGenAsyncService(projectName);
    }

    private OrigineType givenOrigin() throws TechnicalConnectorException {
        McnPackageInfo packageInfo = McnConfigUtil.retrievePackageInfo("genericasync." + projectName);
        CommonBuilder commonBuilder = RequestBuilderFactory.getCommonBuilder(projectName);
        return CommonInputMapper.mapOrigin(commonBuilder.createOrigin(packageInfo));
    }


    public void performGetAndConfirm(boolean confirmMessages) throws Exception {
        OrigineType origin = givenOrigin();
        GenAsyncService service = givenService();

        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);
        }
    }

    private GetResponse getMessages(OrigineType origin, GenAsyncService service) throws URISyntaxException, TechnicalConnectorException, GenAsyncBusinessConnectorException, InstantiationException, IOException {
        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), getResponseHeader);

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

        writeResultsToFile(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 writeResultsToFile(GetResponse getResponse) throws TechnicalConnectorException, IOException {
        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 (unwrappedMessageByteArray != null & unwrappedMessageByteArray.length > 0) {

                // handle the business message with your own logic
                FileTestUtils.writeToFile(new String(unwrappedMessageByteArray), "getResponseBusinessMessage", outputFileExtension);
                LOG.debug("received Business Message : " + new String(unwrappedMessageByteArray));
            }
        }
        for (TAckResponse tackResponse : getResponse.getReturn().getTAckResponses()) {
            byte[] tackResponseBytes = tackResponse.getTAck().getValue();
            if (tackResponseBytes != null & tackResponseBytes.length > 0) {

                // handle with your own logic
                FileTestUtils.writeToFile(new String(tackResponseBytes), "tackResponseValue", outputFileExtension);
                LOG.debug("received Tack response : " + new String(tackResponseBytes));
            }
        }
        FileTestUtils.writeToFile(new String(ConnectorXmlUtils.toByteArray(getResponse)), "getResponse", outputFileExtension);
        ConnectorXmlUtils.dump(getResponse);
    }


    public static final class Builder {
        private String projectName;
        private String outputFileExtension;
        private int maxMessages;
        private List<String> messageNames = new ArrayList<String>();

        private Builder() {
        }

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

        public Builder outputFileExtension(String outputFileExtension) {
            this.outputFileExtension = outputFileExtension;
            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 GetAndConfirmTestHelper build() {
            return new GetAndConfirmTestHelper(this);
        }
    }
}
