/*
 * Copyright (c) Smals
 */
package be.ehealth.businessconnector.test.genericasync.integration;

import be.cin.mycarenet.esb.common.v2.CommonInput;
import be.cin.mycarenet.esb.common.v2.OrigineType;
import be.cin.nip.async.generic.*;
import be.ehealth.business.mycarenetdomaincommons.builders.BlobBuilder;
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.exception.InvalidBlobContentConnectorException;
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.builders.ResponseObjectBuilder;
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.genericasync.test.util.TestUtil;
import be.ehealth.technicalconnector.exception.ConnectorException;
import be.ehealth.technicalconnector.exception.SessionManagementException;
import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.handler.domain.WsAddressingHeader;
import be.ehealth.technicalconnector.idgenerator.IdGeneratorFactory;
import be.ehealth.technicalconnector.session.Session;
import be.ehealth.technicalconnector.utils.ConnectorIOUtils;
import be.ehealth.technicalconnector.utils.ConnectorXmlUtils;
import be.ehealth.technicalconnector.utils.SessionUtil;
import be.fgov.ehealth.technicalconnector.tests.session.SessionInitializer;
import org.apache.commons.lang3.StringUtils;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mapstruct.factory.Mappers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.ws.soap.SOAPFaultException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static org.junit.Assert.fail;


/**
 * Parametrized test that runs the different external test scenarios , as described in Testen externe scenarios GMD - VT0.3. see intermut ,
 * https://share.intermut.be/home/MyCareNet/chapterIV/extranet/pages/Applications.aspx
 * 
 * 
 * @author EHPs
 * 
 */
@RunWith(Parameterized.class)
public final class ParametrizedExternalScenariosIntegrationTest {

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

    /**
     * confirm retrieved messages or not , it can be usefull not to do this , to keep receiving the messages for test purposes.
     */
    private static final boolean CONFIRM_MESSAGES = false;

    private String serviceName;

    private String businessRequestPostLocation;

    private String inszNumberToUse;

    private String messageName;

    /**
     * the mutuality the insznumber is member of.
     */
    private Integer mutuality;


    /**
     * @param businessRequestPostLocation
     * @param inszNumberToUse
     */
    public ParametrizedExternalScenariosIntegrationTest(String businessRequestPostLocation, String inszNumberToUse, Integer mutuality, String messageName, String serviceName) {
        super();
        this.businessRequestPostLocation = businessRequestPostLocation;
        this.inszNumberToUse = inszNumberToUse;
        this.mutuality = mutuality;
        this.messageName = messageName;
        this.serviceName = serviceName;
    }

    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][]{{"/examples/PostConsultationListeXml.xml", "79022050484", 500, "GMD-CONSULT-HCP", "dmg"}, {"/examples/PostConsultationListeXml.xml", "59120346292", 300, "GMD-CONSULT-HCP", "dmg"}};
        return Arrays.asList(data);
    }

    @BeforeClass
    public static void init() throws Exception {
        SessionInitializer.init("/be.ehealth.businessconnector.genericasync.test.properties");
    }

    @AfterClass
    public static void tearDown() throws Exception {
        Session.getInstance().unloadSession();
    }


    @Test
    public void postRequestTestWithDeflateAndOaInHeader() throws InstantiationException, ConnectorException, URISyntaxException {
        performPostRequestTest("deflate", true);
    }

    @Test
    public void postRequestTestWithoutDeflateAndOaInHeader() throws InstantiationException, ConnectorException, URISyntaxException {
        final boolean addOaInHeader = true;
        performPostRequestTest("none", addOaInHeader);
    }

    @Test
    public void postRequestTestWithDeflateAndNoOaInHeader() throws InstantiationException, ConnectorException, URISyntaxException {
        performPostRequestTest("deflate", false);
    }

    @Test
    public void postRequestTestWithoutDeflateAndNoOaInHeader() throws InstantiationException, ConnectorException, URISyntaxException {
        performPostRequestTest("none", false);
    }

    /**
     * @param encodingType
     * @param addOaInHeader
     * @throws TechnicalConnectorException
     * @throws InvalidBlobContentConnectorException
     * @throws GenAsyncBusinessConnectorException
     * @throws InstantiationException
     * @throws ConnectorException
     * @throws URISyntaxException
     * @throws SessionManagementException
     */
    private void performPostRequestTest(final String encodingType, final boolean addOaInHeader) throws TechnicalConnectorException, InvalidBlobContentConnectorException, GenAsyncBusinessConnectorException, InstantiationException, ConnectorException, URISyntaxException, SessionManagementException {
        LOG.debug("PostRequestTest : starting test for mutuality " + mutuality + " and inszNumber " + inszNumberToUse + " for doctor " + getDoctorIdentifierOrNull());
        // Params
        String generatedId = generateRandomIdentifier();
        String xmlTemplate = ConnectorIOUtils.convertStreamToString(ConnectorIOUtils.getResourceAsStream(businessRequestPostLocation));
        xmlTemplate = TestUtil.fillOutPlaceHolders(xmlTemplate);
        BlobBuilder bbuilder = BlobBuilderFactory.getBlobBuilder("genericasync");
        // final String id = "_" + UUID.randomUUID().toString();
        final String id = null;
        // no id needed , since no Xades
        Blob blob = bbuilder.build(xmlTemplate.getBytes(), encodingType, id, "text/xml");
        blob.setMessageName(messageName);

        /*
         * MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = expected; md.update(hash); blob.setHashValue(md.digest());
         */

        LOG.debug("Creation of the get");
        CommonInput ci = Mappers.getMapper(CommonInputMapper.class).map(RequestBuilderFactory.getCommonBuilder("genericasync").createCommonInput(McnConfigUtil.retrievePackageInfo("genericasync.dmg"), false, generatedId));
        be.cin.types.v1.Blob det = DomainBlobMapper.mapBlobToCinBlob(blob);
        // // no xades needed for dmg async

        Post post = BuilderFactory.getRequestObjectBuilder(serviceName).buildPostRequest(ci, det, null);

        LOG.debug("Send of the post request");
        GenAsyncService service = GenAsyncSessionServiceFactory.getGenAsyncService(serviceName);
        WsAddressingHeader header = WsAddressingUtil.createHeader(mutuality.toString(), "urn:be:cin:nip:async:generic:post:msg", IdGeneratorFactory.getIdGenerator("uuid").generateId());

        PostResponse responsePost = service.postRequest(post, header);
        try {
            GenAsyncIntegrationTestUtil.writeToFile(ConnectorXmlUtils.toByteArray(responsePost), "responsePost");
        } catch (IOException e) {
            LOG.error("error writing responsePost to file" + e.getMessage());
        }

        LOG.debug("Call of handler for the post operation");
        ResponseObjectBuilder responseBuilder = BuilderFactory.getResponseObjectBuilder();
        boolean hasWarnings = responseBuilder.handlePostResponse(responsePost);
        if (hasWarnings) {
            LOG.info("The post has warnings");
        } else {
            LOG.info("The post has no warnings");
        }
    }

    /**
     * @return
     */
    private String generateRandomIdentifier() throws TechnicalConnectorException {
        final String generatedId = IdGeneratorFactory.getIdGenerator().generateId();
        return "T" + StringUtils.right(generatedId, 13);
    }


    /**
     * @return
     */
    private String getDoctorIdentifierOrNull() throws TechnicalConnectorException {
        return SessionUtil.getNihii11();
    }

    @Test
    public void postGetAndConfirmTest() throws Exception {
        try {
            LOG.debug("Creation of the get");
            McnPackageInfo packageInfo = McnConfigUtil.retrievePackageInfo(serviceName);
            CommonBuilder commonBuilder = RequestBuilderFactory.getCommonBuilder(serviceName);
            OrigineType origin = Mappers.getMapper(CommonInputMapper.class).map(commonBuilder.createOrigin(packageInfo));
            GenAsyncService service = GenAsyncSessionServiceFactory.getGenAsyncService(serviceName);
            Thread.sleep(750);
            GetResponse responseGet = getMessages(origin, service);
            if (responseGet.getReturn().getMsgCount() == 0 && responseGet.getReturn().getTAckCount() == 0) {
                LOG.error("NO MESSAGES TO CONFIRM ");
            } else if (CONFIRM_MESSAGES) {
                List<byte[]> msgHashValues = getMessageHashValues(responseGet);
                List<byte[]> tackHashValues = getTackHashValues(responseGet);
                LOG.debug("Creation of the confirm");
                ConfirmResponse confirmResponse = confirmTheseMessages(origin, service, msgHashValues, tackHashValues);
                LOG.debug(ConnectorXmlUtils.toString(confirmResponse));
                Thread.sleep(750);
                Assert.assertNotNull(confirmResponse);
                // now you shouldn't receive those messages anymore
                boolean stillMessagesPresent = checkIfMessagesAreStillReturned(origin, service, msgHashValues, tackHashValues);
                if (stillMessagesPresent) {
                    // someTimes they are processed a bit slower, so we retry a few times
                    Thread.sleep(750);
                    LOG.error("still some messages , sleeping 750 ms");
                    stillMessagesPresent = checkIfMessagesAreStillReturned(origin, service, msgHashValues, tackHashValues);
                    if (stillMessagesPresent) {
                        LOG.error("still some messages , sleeping 3000 ms");
                        Thread.sleep(3000);
                        stillMessagesPresent = checkIfMessagesAreStillReturned(origin, service, msgHashValues, tackHashValues);
                    }
                }
                Assert.assertFalse("there were still messages received which were acknowledged", stillMessagesPresent);
            }
        } catch (
                SOAPFaultException e) {
            if (e.getFault().getFaultString().startsWith("Not enough time between two calls")) {
                fail(e.getFault().getFaultString());
            } else {
                throw e;
            }
        }
    }


    /**
     * @param origin
     * @param service
     * @param msgHashValues
     * @param tackHashValues
     * @param stillMessagesPresent
     * @return
     * @throws URISyntaxException
     * @throws TechnicalConnectorException
     * @throws GenAsyncBusinessConnectorException
     * @throws SessionManagementException
     * @throws InstantiationException
     */
    private boolean checkIfMessagesAreStillReturned(OrigineType origin, GenAsyncService service, List<byte[]> msgHashValues, List<byte[]> tackHashValues) throws URISyntaxException, TechnicalConnectorException, GenAsyncBusinessConnectorException, SessionManagementException, InstantiationException {
        boolean stillMessagesPresent = false;
        final GetResponse messages = getMessages(origin, service);
        final Responses messageReturn = messages.getReturn();
        if (messageReturn.getMsgCount() != 0 || messageReturn.getTAckCount() != 0) {
            LOG.debug("Service is still returning messages for these ids : ");
            for (MsgResponse msgResponse : messageReturn.getMsgResponses()) {
                final byte[] hashValue = msgResponse.getDetail().getHashValue();
                if (msgHashValues.contains(hashValue)) {
                    final String msg = "\tmessageResponse found even after its reception was already confirmed : hashValue: >" + hashValue + "< , inputRef : " + msgResponse.getCommonOutput().getInputReference() + " outputRef: " + msgResponse.getCommonOutput().getOutputReference();
                    LOG.debug(msg);
                    Assert.fail(msg);
                }
            }
            for (TAckResponse tackResp : messageReturn.getTAckResponses()) {
                final byte[] value = tackResp.getTAck().getValue();
                if (tackHashValues.contains(value)) {
                    final String msg = "\tTackResponse found after it was already confirmed : hashValue: >" + value + "< , appliesTo >" + tackResp.getTAck().getAppliesTo() + "< " + tackResp.getTAck().getResultMessage();
                    LOG.debug(msg);
                    stillMessagesPresent = true;
                }
            }
        }
        return stillMessagesPresent;
    }

    /**
     * @param origin
     * @param service
     * @return
     * @throws URISyntaxException
     * @throws TechnicalConnectorException
     * @throws GenAsyncBusinessConnectorException
     * @throws SessionManagementException
     * @throws InstantiationException
     */
    private GetResponse getMessages(OrigineType origin, GenAsyncService service) throws URISyntaxException, TechnicalConnectorException, GenAsyncBusinessConnectorException, SessionManagementException, InstantiationException {
        MsgQuery msgQuery = new MsgQuery();
        msgQuery.setInclude(true);
        msgQuery.setMax(100);
        // if no messages are added , all messages are returned
        msgQuery.getMessageNames().clear();
        msgQuery.getMessageNames().add("GMD-CONSULT-HCP");
        msgQuery.getMessageNames().add("GMD-CLOSURE");
        msgQuery.getMessageNames().add("GMD-EXTENSION");

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

        LOG.debug("Send of the get request");
        WsAddressingHeader responseGetHeader = new WsAddressingHeader(new URI("urn:be:cin:nip:async:generic:get:query"));
        responseGetHeader.setMessageID(new URI(IdGeneratorFactory.getIdGenerator("uuid").generateId()));
        GetResponse responseGet = service.getRequest(BuilderFactory.getRequestObjectBuilder(serviceName).buildGetRequest(origin, msgQuery, tackQuery), responseGetHeader);
        try {
            GenAsyncIntegrationTestUtil.writeToFile(ConnectorXmlUtils.toByteArray(responseGet), "responseGet");
        } catch (IOException e) {
            LOG.error("error writing responseGet to file " + e.getMessage());
        }
        return responseGet;
    }

    /**
     * @param responseGet
     * @return
     */
    private List<byte[]> getTackHashValues(GetResponse responseGet) {
        List<byte[]> tackHashValues = new ArrayList<byte[]>();
        for (TAckResponse tackResponse : responseGet.getReturn().getTAckResponses()) {
            final byte[] hashValue = tackResponse.getTAck().getValue();
            LOG.debug("adding confirm for tack hash >" + hashValue + "<");
            tackHashValues.add(hashValue);
        }
        return tackHashValues;
    }

    /**
     * @param responseGet
     * @return
     */
    private List<byte[]> getMessageHashValues(GetResponse responseGet) {
        List<byte[]> msgHashValues = new ArrayList<byte[]>();
        for (MsgResponse msgResp : responseGet.getReturn().getMsgResponses()) {
            final byte[] hashValue = msgResp.getDetail().getHashValue();
            LOG.debug("adding confirm for msg hash >" + hashValue + "<");
            msgHashValues.add(hashValue);
        }
        return msgHashValues;
    }

    /**
     * @param origin
     * @param service
     * @param msgHashValues
     * @param tackHashValues
     * @return
     * @throws URISyntaxException
     * @throws TechnicalConnectorException
     * @throws GenAsyncBusinessConnectorException
     * @throws SessionManagementException
     */
    private ConfirmResponse confirmTheseMessages(OrigineType origin, GenAsyncService service, List<byte[]> msgHashValues, List<byte[]> tackHashValues) throws URISyntaxException, TechnicalConnectorException, GenAsyncBusinessConnectorException, SessionManagementException {
        WsAddressingHeader responseConfirmHeader = new WsAddressingHeader(new URI("urn:be:cin:nip:async:generic:confirm:hash"));
        responseConfirmHeader.setTo(new URI(""));
        responseConfirmHeader.setFaultTo("http://www.w3.org/2005/08/addressing/anonymous");
        responseConfirmHeader.setReplyTo("http://www.w3.org/2005/08/addressing/anonymous");
        responseConfirmHeader.setMessageID(new URI(IdGeneratorFactory.getIdGenerator("uuid").generateId()));

        Confirm request = new Confirm();
        request.setOrigin(origin);
        request.getMsgHashValues().addAll(msgHashValues);
        request.getTAckContents().addAll(tackHashValues);
        ConfirmResponse confirmResponse = service.confirmRequest(request, responseConfirmHeader);
        return confirmResponse;
    }


}
