

package gov.va.med.cds.exceptionframework;


import gov.va.med.cds.clinicaldata.vhim400.ClinicalDataResponseInterface;
import gov.va.med.cds.clinicaldata.vhim400.PathwaysDataResponseInterface;
import gov.va.med.cds.exception.AbstractCdsBaseException;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exception.InitializationException;
import gov.va.med.cds.exception.SystemException;
import gov.va.med.cds.request.ErrorSectionHelperInterface;
import gov.va.med.cds.request.ErrorSectionHelperInterface.ErrorType;
import gov.va.med.cds.template.TemplateHelperInterface;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.util.Map;


public class ExceptionHandler
    implements
        InitializingBean
{
    private static final Log logger = LogFactory.getLog( ExceptionHandler.class );

    private static final String REQUEST_ID_NOT_SET = "request_id_not_set";
    private static final String CDS_RESPONSE_TEMPLATE_ID = "CdsResponse400";
    private static final String PATHWAYS_RESPONSE_TEMPLATE_ID = "PathwaysResponse400";

    private static String cdsAppName = null;
    private static String pathwaysAppName = null;
    private static ExceptionLoggerInterface guaranteedLogger = null;
    private static TemplateHelperInterface templateHelper = null;
    private static ClinicalDataResponseInterface clinicalDataResponse = null;
    private static PathwaysDataResponseInterface pathwaysDataResponse = null;
    private static Map<String, PathwaysDataResponseInterface> pathwaysConfigurableResponseMap = null;
    private static Map<String, String> pathwaysFilterTemplateMap = null;


    /**
     * Handle Exception and create an empty clinical data document with an appropriate errorSection element for write path
     * 
     * @param exception
     * @param templateId
     * @param requestId
     * @param applicationName
     * @return
     */
    public static Document handleException( Exception exception, String templateId, String requestId, String applicationName )
    {
        return handleExceptionInternal( null, exception, templateId, requestId, applicationName );
    }


    /**
     * Handle Exception and create an empty clinical data document with an appropriate errorSection element for read path based on applicationame
     * @param exception
     * @param templateId
     * @param filterRequest
     * @param filterId
     * @param requestId
     * @param applicationName
     * @return
     */
    public static Document handleException( Exception exception, String templateId, String filterRequest, String filterId, String requestId,
                    String applicationName )
    {
        return handleExceptionInternal( null, exception, templateId, filterRequest, filterId, requestId, applicationName );
    }


    /**
     * Handle Exception and add an appropriate errorSection element to an existing clinical data document.
     * 
     * @param clinicalDataDocument
     * @param exception
     * @param templateId
     * @param requestId
     * @param applicationName
     * @return
     */
    public static Document handleException( Document clinicalDataDocument, Exception exception, String templateId, String requestId,
                    String applicationName )
    {
        return handleExceptionInternal( clinicalDataDocument, exception, templateId, requestId, applicationName );
    }


    /**
     * used in write path
     * @param clinicalDataDocument
     * @param exception
     * @param templateId
     * @param requestId
     * @param applicationName
     * @return
     */
    private static Document handleExceptionInternal( Document clinicalDataDocument, Exception exception, String templateId, String requestId,
                    String applicationName )
    {
        // Check parameters for missing values and set defaults
        if ( !StringUtils.hasText( templateId ) )
        {
            if ( applicationName.equals( cdsAppName ) )
            {
                templateId = CDS_RESPONSE_TEMPLATE_ID;
            }
            else if ( applicationName.equals( pathwaysAppName ) )
            {
                templateId = PATHWAYS_RESPONSE_TEMPLATE_ID;
            }
        }
       
        if ( !StringUtils.hasText( requestId ) )
        {
            requestId = REQUEST_ID_NOT_SET;
        }

        if ( !StringUtils.hasText( applicationName ) )
        {
           applicationName = templateHelper.getApplicationName(templateId);
        }

        // Create an empty clinical document, if needed
        if ( clinicalDataDocument == null )
        {
            clinicalDataDocument = createClinicalDataDocument( templateId, requestId, applicationName );
        }

        if ( exception instanceof AbstractCdsBaseException )
        {
            clinicalDataDocument = handleCdsException( ( AbstractCdsBaseException )exception, clinicalDataDocument, templateId, requestId,
                            applicationName );
        }
        else
        {
            // don't have CDS error codes, so handle them specially.
            ErrorCodeEnum errorCodeEnum = ErrorCodeEnum.ROOT_CAUSE_MSG;
            String[] msg = { "Non CDS exception occured in CDS Application: " + exception.getClass().getName() + " The message is: "
                            + exception.getMessage() };
            String clientMessage = ExceptionUtil.formatMessage( errorCodeEnum.getClientMessage(), msg );
            SystemException se = new SystemException( errorCodeEnum, exception, clientMessage );
            clinicalDataDocument = handleCdsException( se, clinicalDataDocument, templateId, requestId, applicationName );
        }

        return clinicalDataDocument;
    }


    /**
     * Used in case of read path
     * @param clinicalDataDocument
     * @param exception
     * @param templateId
     * @param requestXml
     * @param filterId
     * @param requestId
     * @param applicationName
     * @return
     */
    private static Document handleExceptionInternal( Document clinicalDataDocument, Exception exception, String templateId, String requestXml,
                    String filterId, String requestId, String applicationName )
    {
        // Check parameters for missing values and set defaults
        if ( !StringUtils.hasText( templateId ) )
        {
            if ( applicationName.equals( cdsAppName ) )
            {
                templateId = CDS_RESPONSE_TEMPLATE_ID;
            }
            else if ( applicationName.equals( pathwaysAppName ) )
            {
                templateId = getPathwaysTemplateId( templateId, requestXml, filterId, requestId, applicationName );
            }
        }

        if ( !StringUtils.hasText( requestId ) )
        {
            requestId = REQUEST_ID_NOT_SET;
        }

        if ( !StringUtils.hasText( applicationName ) )
        {
            applicationName = cdsAppName;
        }

        // Create an empty clinical document, if needed
        if ( clinicalDataDocument == null )
        {
            ErrorCodeEnum errorCodeEnum = ( ( AbstractCdsBaseException )exception ).getErrorCode();
            if ( errorCodeEnum.equals( errorCodeEnum.READ_REQUEST_INPUT_PARAMETERS_NULL ) )
            {
                clinicalDataDocument = createClinicalDataDocument( templateId, requestXml, filterId, requestId, applicationName );
            }
            else
            {
                clinicalDataDocument = createClinicalDataDocument( templateId, requestId, applicationName );
            }
        }

        if ( exception instanceof AbstractCdsBaseException )
        {
            clinicalDataDocument = handleCdsException( ( AbstractCdsBaseException )exception, clinicalDataDocument, templateId, requestId,
                            applicationName );
        }
        else
        {
            // don't have CDS error codes, so handle them specially.
            ErrorCodeEnum errorCodeEnum = ErrorCodeEnum.ROOT_CAUSE_MSG;
            String[] msg = { "Non CDS exception occured in CDS Application: " + exception.getClass().getName() + " The message is: "
                            + exception.getMessage() };
            String clientMessage = ExceptionUtil.formatMessage( errorCodeEnum.getClientMessage(), msg );
            SystemException se = new SystemException( errorCodeEnum, exception, clientMessage );
            clinicalDataDocument = handleCdsException( se, clinicalDataDocument, templateId, requestId, applicationName );
        }
        return clinicalDataDocument;
    }


    private static String getPathwaysTemplateId( String templateId, String requestXml, String filterId, String requestId, String applicationName )
    {
        if ( !StringUtils.hasText( filterId ) )
        {
            if ( StringUtils.hasText( requestXml ) )
            {
                filterId = getFilterIdFromXml( templateId, requestXml, requestId, applicationName );
            }
            if ( !StringUtils.hasText( filterId ) )
            {
                templateId = PATHWAYS_RESPONSE_TEMPLATE_ID;
            }
            else
            {
                templateId = pathwaysFilterTemplateMap.get( filterId );
            }
        }
        else
        {
            templateId = pathwaysFilterTemplateMap.get( filterId );
        }

        if ( !StringUtils.hasText( templateId ) )
        {
            templateId = PATHWAYS_RESPONSE_TEMPLATE_ID;
        }

        return templateId;
    }


    public static void logRootException( Exception exception, String templateId, String requestId, String applicationName )
    {
        ErrorCodeEnum errorCodeEnum = ErrorCodeEnum.ROOT_CAUSE_MSG;
        String[] msg = { "Non CDS exception occured in CDS Application: " + exception.getClass().getName() + " The message is: "
                        + exception.getMessage() };
        String clientMessage = ExceptionUtil.formatMessage( errorCodeEnum.getClientMessage(), msg );
        SystemException se = new SystemException( errorCodeEnum, exception, clientMessage );
        
        ExceptionInfo exceptionInfo = getExceptionInfo( se, clientMessage, errorCodeEnum, errorCodeEnum.getLoggingSeverity() );
        getGuaranteedLogger().log( exceptionInfo, clientMessage, templateId, requestId, applicationName);
    }


    /**
     * Specific to CUAD request processing
     */
    private static Document createClinicalDataDocument( String templateId, String requestId, String applicationName )
    {
        Document clinicalDataDocument = null;
        PathwaysDataResponseInterface pathwaysResponse = null;

        if ( applicationName.equals( pathwaysAppName ) )
        {
            pathwaysResponse = getPathwaysConfigurableDataResponse( templateId );
            if ( pathwaysResponse == null )
            {
                pathwaysResponse = pathwaysDataResponse;
            }

            //do not create patient element ...
            clinicalDataDocument = pathwaysResponse.buildEmptyPathwaysDocumentWithoutPatient( templateId, requestId );
        }
        else if ( applicationName.equals( cdsAppName ) )
        {
            //do not create patient element ...
            clinicalDataDocument = clinicalDataResponse.buildEmptyClinicalDocumentWithoutPatient( templateId, requestId );
        }

        return clinicalDataDocument;
    }


    /**
     * Specific to read request processing
     * @param templateId
     * @param requestXml
     * @param filterId
     * @param requestId
     * @param applicationName
     * @return
     */
    private static Document createClinicalDataDocument( String templateId, String requestXml, String filterId, String requestId,
                    String applicationName )
    {
        Document clinicalDataDocument = null;
        PathwaysDataResponseInterface pathwaysResponse = null;

        if ( applicationName.equals( pathwaysAppName ) )
        {
            pathwaysResponse = getConfiguredPathwaysDataResponse( templateId, requestXml, filterId, requestId, applicationName );

            if ( templateId.equals( PATHWAYS_RESPONSE_TEMPLATE_ID ) )
            {
                //do not create patient element ...
                clinicalDataDocument = pathwaysResponse.buildEmptyPathwaysDocumentWithoutPatient( templateId, requestId );
            }
            else
            {
                clinicalDataDocument = pathwaysResponse.buildEmptyPathwaysDocumentWithPatient( templateId, requestId );
            }
        }
        else if ( applicationName.equals( cdsAppName ) )
        {
            if ( templateId.equals( CDS_RESPONSE_TEMPLATE_ID ) )
            {
                //do not create patient element ...
                clinicalDataDocument = clinicalDataResponse.buildEmptyClinicalDocumentWithoutPatient( templateId, requestId );
            }
            else
            {
                clinicalDataDocument = clinicalDataResponse.buildEmptyClinicalDocument( templateId, requestId );
            }
        }

        return clinicalDataDocument;
    }


    private static PathwaysDataResponseInterface getConfiguredPathwaysDataResponse( String templateId, String requestXml, String filterId,
                    String requestId, String applicationName )
    {
        PathwaysDataResponseInterface pathwaysResponse;
        if ( templateId.equals( PATHWAYS_RESPONSE_TEMPLATE_ID ) )
        {
            if ( !StringUtils.hasText( filterId ) )
            {
                if ( !StringUtils.hasText( requestXml ) )
                {
                    pathwaysResponse = pathwaysDataResponse;
                }
                else
                {
                    filterId = getFilterIdFromXml( templateId, requestXml, requestId, applicationName );

                    pathwaysResponse = getPathwaysConfigurableDataResponse( filterId );
                }
            }
            else
            {
                pathwaysResponse = getPathwaysConfigurableDataResponse( filterId );
            }

        }
        else
        {
            pathwaysResponse = getPathwaysConfigurableDataResponse( templateId );
        }

        if ( pathwaysResponse == null )
        {
            pathwaysResponse = pathwaysDataResponse;
        }
        return pathwaysResponse;
    }


    private static String getFilterIdFromXml( String templateId, String requestXml, String requestId, String applicationName )
    {
        String filterId = null;
        SAXReader reader = new SAXReader();
        Document document = null;
        try
        {
            document = reader.read( new ByteArrayInputStream( requestXml.getBytes() ) );
        }
        catch ( DocumentException e )
        {
            logger.fatal( gov.va.med.cds.util.LogMessageUtil.buildMessage( templateId, requestId, applicationName,
                            "Error parsing filter XML during the read request processing. " ), e );
        }

        if ( document != null )
        {
            Node filterIdNode = document.selectSingleNode( "/filter:filter/filterId" );
            filterId = ( ( Element )filterIdNode ).getText();
        }
        return filterId;
    }


    private static Document handleCdsException( AbstractCdsBaseException cdsException, Document clinicalDocument, String templateId,
                    String requestId, String applicationName )
    {
        ErrorCodeEnum errorCodeEnum = cdsException.getErrorCode();
        String clientMessage = ExceptionUtil.formatMessage( errorCodeEnum.getClientMessage(), cdsException.getCustomMsgValues() );
        if ( cdsException.getCause() != null )
        {
            clientMessage = String.format( "%s%s%s", clientMessage, ": ", cdsException.getCause().getMessage() );
        }

        ExceptionInfo exceptionInfo = getExceptionInfo( cdsException, clientMessage, errorCodeEnum, errorCodeEnum.getLoggingSeverity() );
        getGuaranteedLogger().log( exceptionInfo, clientMessage, templateId, requestId, applicationName);

        ErrorSectionHelperInterface errorSectionHelper = getErrorSectionHelper( templateId );
        if ( errorSectionHelper == null )
        {
            if ( applicationName.equals( cdsAppName ) )
            {
                templateId = CDS_RESPONSE_TEMPLATE_ID;
            }
            else if ( applicationName.equals( pathwaysAppName ) )
            {
                templateId = PATHWAYS_RESPONSE_TEMPLATE_ID;
            }

            //first replace clinicalDocument with a new one without the <patient> element generated for CdsResponse400 templateId
            clinicalDocument = createClinicalDataDocument( templateId, requestId, applicationName );

            //then get default errorSectionHelper for CdsResponse400
            errorSectionHelper = getErrorSectionHelper( templateId );
        }

        return errorSectionHelper.buildErrorSection( clinicalDocument, exceptionInfo, requestId );

    }


    private static ErrorSectionHelperInterface getErrorSectionHelper( String templateId )
    {
        ErrorSectionHelperInterface errorSectionHelper = null;

        if ( StringUtils.hasText( templateId ) )
        {
            errorSectionHelper = templateHelper.getErrorSectionHelper( templateId );
        }
        else
        {
            throw new InitializationException( ErrorCodeEnum.TEMPLATE_ID_NULL );
        }
        //TODO: work with Narasa to redesign this functionality - if we really need it or not
        //        if ( errorSectionHelper == null )
        //        {
        //            throw new InitializationException( ErrorCodeEnum.MISSING_ERROR_SECTION_HELPER_EXCEPTION, templateId );
        //        }

        return errorSectionHelper;
    }


    public static void setGuaranteedLogger( ExceptionLoggerInterface guaranteedLogger )
    {
        ExceptionHandler.guaranteedLogger = guaranteedLogger;
    }


    public static void setTemplateHelper( TemplateHelperInterface templateHelper )
    {
        ExceptionHandler.templateHelper = templateHelper;
    }


    public static void setClinicalDataResponse( ClinicalDataResponseInterface clinicalDataResponse )
    {
        ExceptionHandler.clinicalDataResponse = clinicalDataResponse;
    }


    private static ExceptionInfo getExceptionInfo( Throwable businessException, String clientMessage, ErrorCodeEnum errorCode,
                    LoggingSeverity loggingSeverity )
    {
        ExceptionInfo exceptionInfo = new ExceptionInfo();
        exceptionInfo.setClientMessage( clientMessage );
        exceptionInfo.setErrorCode( errorCode );
        exceptionInfo.setException( businessException );
        exceptionInfo.setExceptionMessage( businessException.getMessage() );
        exceptionInfo.setLoggingSeverity( loggingSeverity );

        return exceptionInfo;
    }


    public static ExceptionLoggerInterface getGuaranteedLogger( )
    {
        return guaranteedLogger;
    }


    @Override
    /**
     * Method called by Spring Framework after context file has been processed.
     * Check for missing dependencies.
     */
    public void afterPropertiesSet( )
        throws Exception
    {
        if ( guaranteedLogger == null )
        {
            throw new InitializationException( ErrorCodeEnum.GUARANTEED_LOGGER_NOT_CONFIGURED_AND_NULL );
        }

        if ( templateHelper == null )
        {
            throw new InitializationException( ErrorCodeEnum.TEMPLATE_HELPER_CONFIGURATION_EXCEPTION, getClass().getName() );
        }

        if ( clinicalDataResponse == null )
        {
            throw new InitializationException( ErrorCodeEnum.MISSING_CLINICAL_DATA_RESPONSE_EXCEPTION, getClass().getName() );
        }

        if ( pathwaysConfigurableResponseMap == null )
        {
            throw new InitializationException( ErrorCodeEnum.MISSING_CONFIGURABLE_PATHWAYS_DATA_RESPONSE_MAP_EXCEPTION, getClass().getName() );
        }
    }


    public static boolean hasErrors( Document clinicalDataDocument, String templateId )
    {
        return ( templateHelper.getErrorSectionHelper( templateId ).getErrorCount( ErrorType.ALL, clinicalDataDocument ) > 0 );
    }


    public static TemplateHelperInterface getTemplateHelper( )
    {
        return templateHelper;
    }


    public static ClinicalDataResponseInterface getClinicalDataResponse( )
    {
        return clinicalDataResponse;
    }


    public static void setPathwaysConfigurableResponseMap( Map<String, PathwaysDataResponseInterface> pathwaysConfigurableResponseMap )
    {
        ExceptionHandler.pathwaysConfigurableResponseMap = pathwaysConfigurableResponseMap;
    }


    public static void setCdsAppName( String cdsAppName )
    {
        ExceptionHandler.cdsAppName = cdsAppName;
    }


    public static void setPathwaysAppName( String pathwaysAppName )
    {
        ExceptionHandler.pathwaysAppName = pathwaysAppName;
    }


    /**
     * Get the configurable response object for based on templateId
     * @param templateId
     * @return
     */
    private static PathwaysDataResponseInterface getPathwaysConfigurableDataResponse( String id )
    {
        PathwaysDataResponseInterface configurablePathwaysDataResponse = null;

        configurablePathwaysDataResponse = pathwaysConfigurableResponseMap.get( id );
        return configurablePathwaysDataResponse;
    }


    public static void setPathwaysDataResponse( PathwaysDataResponseInterface pathwaysDataResponse )
    {
        ExceptionHandler.pathwaysDataResponse = pathwaysDataResponse;
    }


    public static void setPathwaysFilterTemplateMap( Map<String, String> pathwaysFilterTemplateMap )
    {
        ExceptionHandler.pathwaysFilterTemplateMap = pathwaysFilterTemplateMap;
    }

}
