/*
 * Copyright (c) eHealth
 */
package be.ehealth.businessconnector.invoicing;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.mycarenetcommons.builders.BlobBuilderFactory;
import be.ehealth.business.mycarenetcommons.builders.CommonBuilder;
import be.ehealth.business.mycarenetcommons.builders.RequestBuilderFactory;
import be.ehealth.business.mycarenetcommons.domain.Blob;
import be.ehealth.business.mycarenetcommons.mapper.SendRequestMapper;
import be.ehealth.business.mycarenetcommons.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.technicalconnector.config.util.ConfigUtil;
import be.ehealth.technicalconnector.config.util.domain.PackageInfo;
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.utils.ConnectorXmlUtils;
import be.fgov.ehealth.technicalconnector.tests.junit.rule.SessionRule;


/**
 * Test of the operation get and confirm of the invoicing service
 * 
 * @author EHP
 */
public class GetAndConfirmIntegrationTest {

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

    private static final String PROJECT_NAME = "invoicing";

    private static final boolean CONFIRM_MESSAGES = false;

    @Rule
    public SessionRule sessionRule = SessionRule.withActiveSession().baseOn("/be.ehealth.businessconnector.invoicing.test.properties").build();

    @Test
    public void postGetAndConfirmTest() throws Exception {
        LOG.debug("Creation of the get");

        PackageInfo packageInfo = ConfigUtil.retrievePackageInfo("genericasync." + PROJECT_NAME);
        CommonBuilder commonBuilder = RequestBuilderFactory.getCommonBuilder(PROJECT_NAME);
        OrigineType origin = CommonInputMapper.mapOrigin(commonBuilder.createOrigin(packageInfo));

        GenAsyncService service = GenAsyncSessionServiceFactory.getGenAsyncService(PROJECT_NAME);
        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);
        }

    }

    /**
     * @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, IOException {
        LOG.debug("Creation of the get");
        MsgQuery msgQuery = new MsgQuery();
        msgQuery.setInclude(true);
        msgQuery.setMax(100);
        msgQuery.getMessageNames().add("HCPFAC");
        msgQuery.getMessageNames().add("HCPAFD");
        msgQuery.getMessageNames().add("HCPVWR");

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

        LOG.debug("Send of the get request");
        WsAddressingHeader responseGetHeader = WsAddressingUtil.createHeader(null, "urn:be:cin:nip:async:generic:get:query");
        GetResponse responseGet = service.getRequest(BuilderFactory.getRequestObjectBuilder(PROJECT_NAME).buildGetRequest(origin, msgQuery, tackQuery), responseGetHeader);
        // validate the get responses ( including check on xades if present)
        BuilderFactory.getResponseObjectBuilder().handleGetResponse(responseGet);
        for (MsgResponse msgResponse : responseGet.getReturn().getMsgResponses()) {
            Blob mappedBlob = SendRequestMapper.mapToBlob(msgResponse.getDetail());
            byte[] unwrappedMessageByteArray = BlobBuilderFactory.getBlobBuilder("invoicing").checkAndRetrieveContent(mappedBlob);
            if (unwrappedMessageByteArray != null & unwrappedMessageByteArray.length > 0) {
                // handle the business message with your own logic
                writeToFile(unwrappedMessageByteArray, "getResponseBusinessMessage");
                LOG.debug("received Business Message : " + new String(unwrappedMessageByteArray));
            }
        }
        for (TAckResponse tackResponse : responseGet.getReturn().getTAckResponses()) {
            byte[] tackResponseBytes = tackResponse.getTAck().getValue();
            if (tackResponseBytes != null & tackResponseBytes.length > 0) {
                // handle with your own logic
                writeToFile(tackResponseBytes, "tackResponseValue");
                LOG.debug("received Tack response : " + new String(tackResponseBytes));
            }

        }
        writeToFile(ConnectorXmlUtils.toByteArray(responseGet), "getResponse");
        ConnectorXmlUtils.logXmlObject(responseGet);
        return responseGet;
    }

    /**
     * @param content
     * @param location
     * @throws TechnicalConnectorException
     * @throws IOException
     */
    public static void writeToFile(byte[] content, String fileName) throws TechnicalConnectorException, IOException {
        String userHome = System.getProperty("user.home");
        if (userHome == null) {
            throw new IllegalStateException("user.home==null");
        }
        File home = new File(userHome);
        File sendFilesDir = new File(home, "invoicingSendFiles");
        sendFilesDir.mkdir();
        java.io.File contentFile = File.createTempFile(fileName, ".xml", sendFilesDir);
        contentFile.createNewFile();
        LOG.debug("writing content to file on location " + contentFile.getAbsolutePath() + " : " + new String(content));
        FileWriter writer = new FileWriter(contentFile);
        writer.write(new String(content));
        writer.flush();
        writer.close();
    }

    /**
     * @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;
    }

    /**
     * @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, IOException {
        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;
    }

}
