

package gov.va.med.cds.integration.vim.ha;


import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import gov.va.med.cds.integration.AbstractEndToEndTest;
import gov.va.med.cds.junit.runners.Suite;
import gov.va.med.cds.request.TemplateRequestProcessor;
import gov.va.med.cds.testharness.vhim400.TemplateIdHelper;
import gov.va.med.cds.uniqueidentifier.UniqueIdentifier;

import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.junit.Assert;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;


public abstract class AbstractHealthAdapterEndToEndTest
    extends
        AbstractEndToEndTest
{
    protected static final String CLIENT_NAME = "Test Client";
    protected static final String TEST_CUD_PATIENTIDENTITY_HDR = "test_sourceid_5000";
    protected static final String TEST_READ_PATIENTIDENTITY_HDR = "test_sourceid_1000000";
    protected static final String ASSIGNINGAUTHORITY_HDR = "USDOD";
    protected static final String ASSIGNING_FACILITY_HDR = "200DOD";
    protected static final String INVALID_ASSIGNINGAUTHORITY_HDR = "USXXX";
    protected static final String INVALID_ASSIGNING_FACILITY_HDR = "200XXX";
    protected static final String LOGICAL_DELETE_RECORD_STATUS = "1";
    protected static final String DELETE_RECORD_STATUS = "0";
    protected static final String REQUEST_ID_XPATH = "/clinicaldata:ClinicalData/requestId";
    protected static final String PATIENT_IDENTITY_XPATH = "//patient/identifier/identity";
    protected static final String PATIENT_ENTEREDJOURNAL_DOMAINENTRYPOINT_XPATH = "//patientJournalEntries";
    protected static final String CREATED_RECORD_IDENTIFIERS_XPATH = "//recordIdentifiers";
    protected static final String READ_RESPONSE_RECORD_UDPATETIME_XPATH = "//recordUpdateTime";
    protected static final String PAIN_ASSESSMENT_XPATH = "//painAssessments";
    protected static final String START_DATE = "1969-12-31";
    protected static final String CALENDAR_EVENT_XPATH = "//calendarEvent";
    protected static final String FATAL_ERRORS_EXCEPTIONS_XPATH = "/clinicaldata:ClinicalData/errorSection/fatalErrors/exception";
    protected static final String HA_CREATE_TEMPLATE_ID = TemplateIdHelper.HEALTH_ADAPTER_CREATE1_TEMPLATE_ID;
    protected static final String HA_READ_TEMPLATE_ID = TemplateIdHelper.HEALTH_ADAPTER_READ1_TEMPLATE_ID;
    protected static final String HA_UPDATE_TEMPLATE_ID = TemplateIdHelper.HEALTH_ADAPTER_UPDATE1_TEMPLATE_ID;
    protected static final String FILTER_ID = TemplateIdHelper.HEALTH_ADAPTER_SINGLE_PATIENT_ALL_DATA_FILTER;
    protected static final String HA_DELETE_TEMPLATE_ID = TemplateIdHelper.HEALTH_ADAPTER_DELETE1_TEMPLATE_ID;
    protected static final String QUERY_TIMOUT_SECONDS_MIN = "1";
    protected static final String QUERY_TIMOUT_SECONDS_MAX = "300";
    protected String createXmlRequestId = null;
    protected String recordIdentifier = null;
    protected TemplateRequestProcessor requestProcessor;

    @Autowired
    protected JdbcTemplate hdrJdbcTemplate;

    static Logger logger = LogManager.getLogger( AbstractHealthAdapterEndToEndTest.class );


    @Before
    @Suite( groups = { "deprecatedtest" } )
    public void beforeAbstractHealthAdapterEndToEndTestClassSetUp( )
        throws Exception
    {
        requestProcessor = ( TemplateRequestProcessor )getRequestProcessor();
    }


    @SuppressWarnings( "unchecked" )
    protected Document createAndAssertDomainData( String templateId, String createRequestXml )
        throws Exception
    {
        requestProcessor.setValidateReadResponseXML( true );
        createXmlRequestId = new UniqueIdentifier().toString();
        Document clinicalDataDocument = requestProcessor.createClinicalData( createRequestXml, templateId, createXmlRequestId );

        assertNotNull( clinicalDataDocument );

        // Check for an empty error section
        List<Element> errorSections = clinicalDataDocument.selectNodes( XPATH_ERRORSECTION );
        assertTrue( errorSections.size() == 1 );
        Element errorSection = errorSections.get( 0 );
        assertTrue( errorSection.content().size() == 0 );

        String requestIdFromResponse = clinicalDataDocument.selectSingleNode( REQUEST_ID_XPATH ).getText();
        assertTrue( createXmlRequestId.equals( requestIdFromResponse ) );

        // Check for record identifiers in the response for the newly created record(s)
        List<Element> domainElements = clinicalDataDocument.selectNodes( CREATED_RECORD_IDENTIFIERS_XPATH );
        assertTrue( domainElements.size() > 0 );

        List<Element> recordIdentifierList = clinicalDataDocument.selectNodes( RECORD_IDENTIFIER_IDENTITY_XPATH );
        assertTrue( recordIdentifierList.size() > 0 );
        recordIdentifier = ( ( Element )recordIdentifierList.get( 0 ) ).getText();

        return clinicalDataDocument;
    }


    @SuppressWarnings( "unchecked" )
    protected Document createAndAssertExpectedExceptionMessage( String templateId, String createRequestXml, String errorType,
                    String errorElementName, String expectedExceptionMessage )
        throws Exception
    {

        requestProcessor.setValidateReadResponseXML( true );
        createXmlRequestId = new UniqueIdentifier().toString();
        Document clinicalDataDocument = requestProcessor.createClinicalData( createRequestXml, templateId, createXmlRequestId );

        assertNotNull( clinicalDataDocument );

        // Check for an empty error section
        List<Element> errorSections = clinicalDataDocument.selectNodes( XPATH_ERRORSECTION );
        assertTrue( errorSections.size() == 1 );
        Element errorSection = errorSections.get( 0 );
        assertTrue( errorSection.content().size() >= 1 );

        //errorType = fatalErrors, errors, warnings
        Element error = errorSection.element( errorType );
        //errorElementName = errorId, exception, exceptionMessage, errorCode, displayMessage
        String message = error.element( errorElementName ).getText();

        assertTrue( expectedExceptionMessage.equals( message ) );

        return clinicalDataDocument;

    }


    @SuppressWarnings( "unchecked" )
    protected Document readAndAssertDomainData( Map<String, String> assigningFacilityAndPatientIdentityMap, String templateId, String filter,
                    String filterId, String entryPointXpath, int recordCount )
        throws Exception
    {
        requestProcessor.setValidateReadResponseXML( true );
        String requestId = new UniqueIdentifier().toString();
        Document clinicalDataDocument = requestProcessor.readClinicalData( templateId, filter, filterId, requestId );

        assertNotNull( clinicalDataDocument );

        // Check for error sections
        List<Element> errorSections = clinicalDataDocument.selectNodes( XPATH_ERRORSECTION );
        assertTrue( errorSections.size() == 0 );

        String requestIdFromResponse = clinicalDataDocument.selectSingleNode( REQUEST_ID_XPATH ).getText();
        assertTrue( requestId.equals( requestIdFromResponse ) );
        assertTrue( !createXmlRequestId.equals( requestIdFromResponse ) );

        List<Element> patientsList = clinicalDataDocument.selectNodes( PATIENT_IDENTITY_XPATH );
        assertTrue( patientsList.size() > 0 );

        // Check for domain entry point elements
        List<Element> domainElements = clinicalDataDocument.selectNodes( entryPointXpath );
        assertTrue( domainElements.get( 0 ).elements().size() >= recordCount );

        // Check for 'recordUpdateTime', shouldn't be any in the read response
        List<Element> recordUpdateTime = clinicalDataDocument.selectNodes( READ_RESPONSE_RECORD_UDPATETIME_XPATH );
        assertTrue( recordUpdateTime.size() == 0 );

        return clinicalDataDocument;
    }


    protected Document updateAndAssertDomainData( String templateId, String updateRequestXml, String tableName, String columnName )
        throws Exception
    {
        requestProcessor.setValidateReadResponseXML( true );
        String requestId = new UniqueIdentifier().toString();
        Document clinicalDataDocument = requestProcessor.updateClinicalData( updateRequestXml, templateId, requestId );

        assertNotNull( clinicalDataDocument );

        checkResponse( requestId, clinicalDataDocument, tableName, columnName, true );

        return clinicalDataDocument;
    }


    protected Document deleteAndAssertDomainData( String templateId, String deleteRequestXml, String tableName, String columnName,
                    boolean isRequestIdChk )
        throws Exception
    {
        requestProcessor.setValidateReadResponseXML( true );

        String requestId = new UniqueIdentifier().toString();
        Document clinicalDataDocument = requestProcessor.deleteClinicalData( deleteRequestXml, templateId, requestId );
        assertNotNull( clinicalDataDocument );

        checkResponse( requestId, clinicalDataDocument, tableName, columnName, isRequestIdChk );

        return clinicalDataDocument;
    }


    @SuppressWarnings( "unchecked" )
    protected Document readAndAssertDomainData( String templateId, String filterId, String filter )
        throws Exception
    {
        requestProcessor.setValidateReadResponseXML( true );
        String requestId = new UniqueIdentifier().toString();
        Document clinicalDataDocument = requestProcessor.readClinicalData( templateId, filter, filterId, requestId );
        assertNotNull( clinicalDataDocument );

        // Check for error sections
        List<Element> errorSections = clinicalDataDocument.selectNodes( XPATH_ERRORSECTION );
        assertTrue( errorSections.size() == 0 );

        String requestIdFromResponse = clinicalDataDocument.selectSingleNode( REQUEST_ID_XPATH ).getText();
        assertTrue( requestId.equals( requestIdFromResponse ) );
        //assertTrue( !createXmlRequestId.equals( requestIdFromResponse ) );

        return clinicalDataDocument;
    }


    protected Document readAndAssertDomainData( String filter )
        throws Exception
    {
        return readAndAssertDomainData( HA_READ_TEMPLATE_ID, FILTER_ID, filter );
    }


    @SuppressWarnings( "unchecked" )
    private void checkResponse( String requestId, Document clinicalDataDocument, String tableName, String columnName, boolean isRequestIdChk )
    {
        // Check for an empty error section
        List<Element> errorSections = clinicalDataDocument.selectNodes( XPATH_ERRORSECTION );
        assertTrue( errorSections.size() == 1 );
        Element errorSection = errorSections.get( 0 );
        assertTrue( errorSection.content().size() == 0 );

        String requestIdFromResponse = clinicalDataDocument.selectSingleNode( REQUEST_ID_XPATH ).getText();
        assertTrue( requestId.equals( requestIdFromResponse ) );

        assertTrue( !createXmlRequestId.equals( requestIdFromResponse ) );

        if ( isRequestIdChk )
        {
            verifyRequestId( recordIdentifier, tableName, columnName );
        }
    }


    protected void verifyRequestId( String recordIdentifier, String tableName, String columnName )
    {
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append( "SELECT COUNT(*) FROM " + tableName );
        sqlBuilder.append( " WHERE " + columnName + " = '" );
        sqlBuilder.append( recordIdentifier );
        sqlBuilder.append( "'" );
        sqlBuilder.append( " AND REQUEST_ID = '" );
        sqlBuilder.append( createXmlRequestId );
        sqlBuilder.append( "'" );

        int retVal = hdrJdbcTemplate.queryForObject( sqlBuilder.toString(), Integer.class );

        Assert.assertTrue( retVal == 1 );
    }


    /**
     * <b>pretty prints the string representation of an xml document to the configured cds.test.outputdir property value.</b>
     * <p>
     * This method checks the System properties normally configured in the [firstName].[lastName].local.properties file
     * for a property called cds.test.outputdir.  If no property is found, then no pretty print will occur.  If the property value
     * is found, then the existence of the outputdir will be checked.  If it does not exist, then an attempt will be made to 
     * create the directory structure.  If the mkdirs() attempt fails, then no output will be written. If all checks succeed,
     * then the filename argument will be appended to the cds.test.outputdir value and the Document will be pretty printed to 
     * the designated file. 
     * <p>
     * This method is intended to be used to enable faster debugging and analysis for developers. 
     * 
     * @param document the org.dom4j.Document to pretty print
     * @param filename the name of the output file (will be appended to a defined cds.test.outputdir value)
     * @throws IllegalArgumentException if the xmlDocument is null or empty, or if the filename is null or empty
     * @throws IllegalArgumentException if the xmlDocument cannot be parsed as a valid xml document
     */
    public static void prettyPrint( String xmlDocument, String filename )
    {
        if ( xmlDocument == null )
            throw new IllegalArgumentException( "xmlDocument argument cannot be null" );
        if ( xmlDocument.trim().equals( "" ) )
            throw new IllegalArgumentException( "xmlDocument argument cannot be an empty string" );
        if ( filename == null )
            throw new IllegalArgumentException( "filename argument cannot be null" );
        if ( filename.trim().equals( "" ) )
            throw new IllegalArgumentException( "filename argument cannot be empty string" );

        Document document = null;

        try
        {
            document = DocumentHelper.parseText( xmlDocument );
        }
        catch ( DocumentException e )
        {
            throw new IllegalArgumentException( "xmlDocument argument could not be parsed", e );
        }
        prettyPrint( document, filename );
    }


    /**
     * <b>pretty prints the org.dom4j.Document to the configured cds.test.outputdir property value.</b>
     * This method checks the System properties normally configured in the [firstName].[lastName].local.properties file
     * for a property called cds.test.outputdir.  If no property is found, then no pretty print will occur.  If the property value
     * is found, then the existence of the outputdir will be checked.  If it does not exist, then an attempt will be made to 
     * create the directory structure.  If the mkdirs() attempt fails, then no output will be written. If all checks succeed,
     * then the filename argument will be appended to the cds.test.outputdir value and the Document will be pretty printed to 
     * the designated file. 
     * <p>
     * This method is intended to be used to enable faster debugging and analysis for developers. 
     * 
     * @param document the org.dom4j.Document to pretty print
     * @param filename the name of the output file (will be appended to a defined cds.test.outputdir value)
     * @throws IllegalArgumentException if the Document is null, or the filename is null or empty
     */
    public static void prettyPrint( Document document, String filename )
    {
        prettyPrintToCdsTestOutputDir( document, filename );
    }


    /**
     * <b>pretty prints the org.dom4j.Document to the configured cds.test.outputdir property value.</b>
     * This method checks the System properties normally configured in the [firstName].[lastName].local.properties file
     * for a property called cds.test.outputdir.  If no property is found, then no pretty print will occur.  If the property value
     * is found, then the existence of the outputdir will be checked.  If it does not exist, then an attempt will be made to 
     * create the directory structure.  If the mkdirs() attempt fails, then no output will be written. If all checks succeed,
     * then the filename argument will be appended to the cds.test.outputdir value and the Document will be pretty printed to 
     * the designated file. 
     * <p>
     * This method is intended to be used to enable faster debugging and analysis for developers. 
     * 
     * @param document the org.dom4j.Document to pretty print
     * @param filename the name of the output file (will be appended to a defined cds.test.outputdir value)
     * @throws IllegalArgumentException if the Document is null, or the filename is null or empty
     */
    public static void prettyPrintToCdsTestOutputDir( Document document, String filename )
    {
        /// if the developer has not defined the cds.test.outputdir in his properties file, then ignore this method
        String cdsTestOutputDirectory = System.getProperty( "cds.test.outputdir" );
        if ( cdsTestOutputDirectory == null )
        {
            if ( logger.isDebugEnabled() )
            {
                logger.debug( "no cds.test.outputdir property found. To use prettyPrintToTestOutputDirectory() method, add cds.test.outputdir to your *.local.properties file" );
            }
            return;
        }

        /// throw exception if method arguments are invalid
        if ( document == null )
            throw new IllegalArgumentException( "Document argument cannot be null" );
        if ( filename == null )
            throw new IllegalArgumentException( "filename argument cannot be null" );
        if ( filename.trim().equals( "" ) )
            throw new IllegalArgumentException( "filename argument cannot be empty string" );
        filename = filename.trim();

        /// enable the user to add further directories to output
        String separator = File.separator;
        if ( separator.equals( "\\" ) )
            separator += "\\";
        String[] dirs = filename.split( separator );
        if ( dirs.length > 1 )
        {
            filename = dirs[dirs.length - 1]; /// filename is the last element 
            while ( cdsTestOutputDirectory.endsWith( File.separator ) )
            {
                cdsTestOutputDirectory = cdsTestOutputDirectory.substring( 0, cdsTestOutputDirectory.lastIndexOf( File.separator ) );
            }
            for ( int i = 0; i < ( dirs.length - 1 ); i++ )
            {
                String directory = dirs[i].trim();
                if ( directory.equals( "" ) )
                    continue;
                cdsTestOutputDirectory += File.separator + directory;
            }
        }

        try
        {

            /// check for the existence of the output directory, if not present, then try to create it, 
            /// if output directory creation fails, then write a warning message and return without an exception
            File dir = new File( cdsTestOutputDirectory );
            if ( !dir.isDirectory() )
            {
                boolean success = dir.mkdirs();
                if ( !success )
                {
                    if ( logger.isDebugEnabled() )
                    {
                        logger.debug( "unable to create cds.test.outputdir=" + cdsTestOutputDirectory );
                    }
                    return;
                }
            }
            String filepath = cdsTestOutputDirectory + ( cdsTestOutputDirectory.endsWith( File.separator ) ? "" : File.separator ) + filename
                            + ( filename.trim().toLowerCase().endsWith( ".xml" ) ? "" : ".xml" );

            OutputFormat format = OutputFormat.createPrettyPrint();
            XMLWriter writer = new XMLWriter( new FileOutputStream( filepath ), format );
            writer.write( document );
            writer.flush();
            writer.close();
        }
        catch ( Exception e )
        {
            if ( logger.isDebugEnabled() )
            {
                logger.debug( "If a problem exists writing to the cds.test.outputdir property in this environment, then it is recommended to remove the cds.test.outputdir property since this is intended for developer use only",
                                e );
            }
        }
    }
}
