/*******************************************************************************
 * (c) Copyright 2017 EntIT Software LLC, a Micro Focus company
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including without 
 * limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to 
 * whom the Software is furnished to do so, subject to the following 
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
 * IN THE SOFTWARE.
 ******************************************************************************/
package com.fortify.bugtracker.src.ssc.processor;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import com.fortify.bugtracker.common.src.processor.ISourceProcessorSubmitVulnsToTarget;
import com.fortify.bugtracker.common.src.updater.INewIssueVulnerabilityUpdater;
import com.fortify.bugtracker.common.ssc.cli.ICLIOptionsSSC;
import com.fortify.bugtracker.common.ssc.connection.SSCConnectionFactory;
import com.fortify.bugtracker.common.ssc.json.preprocessor.enrich.SSCJSONMapEnrichWithRevisionFromDetails;
import com.fortify.bugtracker.common.ssc.json.preprocessor.filter.SSCJSONMapFilterHasBugURL;
import com.fortify.bugtracker.common.tgt.issue.TargetIssueLocatorAndFields;
import com.fortify.bugtracker.common.tgt.processor.ITargetProcessorSubmitIssues;
import com.fortify.bugtracker.src.ssc.config.SSCSourceVulnerabilitiesConfiguration;
import com.fortify.client.ssc.api.SSCBugTrackerAPI;
import com.fortify.client.ssc.api.SSCCustomTagAPI;
import com.fortify.client.ssc.api.query.builder.SSCApplicationVersionIssuesQueryBuilder;
import com.fortify.client.ssc.api.query.builder.SSCApplicationVersionIssuesQueryBuilder.QueryMode;
import com.fortify.client.ssc.connection.SSCAuthenticatingRestConnection;
import com.fortify.processrunner.cli.CLIOptionDefinition;
import com.fortify.processrunner.cli.CLIOptionDefinitions;
import com.fortify.processrunner.context.Context;
import com.fortify.processrunner.context.ContextSpringExpressionUtil;
import com.fortify.processrunner.processor.IProcessor;
import com.fortify.util.rest.json.preprocessor.filter.AbstractJSONMapFilter.MatchMode;
import com.fortify.util.rest.query.AbstractRestConnectionQueryBuilder;

/**
 * TODO Update JavaDoc?
 * <p>This {@link IProcessor} implementation combines and configures 
 * {@link SSCSourceProcessorRetrieveVulnerabilities}, {@link SSCSourceVulnerabilitiesConfiguration} 
 * and {@link ITargetProcessorSubmitIssues} (provided by the bug tracker 
 * implementation) to allow for submitting SSC vulnerabilities to bug trackers or
 * other external systems.</p> 
 * 
 * <p>This combined configuration will retrieve all open SSC vulnerabilities based on
 * configured search/filtering criteria, optionally group the vulnerabilities based on
 * a configurable grouping expression (if supported by the bug tracker implementation),
 * and then submit the grouped vulnerabilities to the bug tracker or other external system.
 * Optionally, the bug tracker issue link can be stored in SSC for each submitted 
 * vulnerability, for state management purposes (see {@link SSCSourceProcessorUpdateVulnsOnTarget}) and
 * to allow the user to navigate back and forth between SSC and bug tracker. Additional
 * custom tags to hold information about the submitted bug tracker issue can be 
 * configured as well.</p> 
 * 
 * @author Ruud Senden
 */
@Component
public class SSCSourceProcessorSubmitVulnsToTarget extends AbstractSSCSourceVulnerabilityProcessor implements ISourceProcessorSubmitVulnsToTarget, INewIssueVulnerabilityUpdater {
	private static final Log LOG = LogFactory.getLog(SSCSourceProcessorSubmitVulnsToTarget.class);
	
	@Override
	protected void addSourceCLIOptionDefinitions(CLIOptionDefinitions cliOptionDefinitions) {
		if ( getConfiguration().isAddNativeBugLink() ) {
			cliOptionDefinitions.add(new CLIOptionDefinition("SSC", ICLIOptionsSSC.PRP_SSC_BUG_TRACKER_USER_NAME, "SSC "+getConfiguration().getAddNativeBugLinkBugTrackerName()+" bug tracker user name (required if SSC bug tracker requires authentication)", false));
			cliOptionDefinitions.add(new CLIOptionDefinition("SSC", ICLIOptionsSSC.PRP_SSC_BUG_TRACKER_PASSWORD, "SSC "+getConfiguration().getAddNativeBugLinkBugTrackerName()+" bug tracker password", false).isPassword(true).dependsOnOptions(ICLIOptionsSSC.PRP_SSC_BUG_TRACKER_USER_NAME));
		}
	}
	
	@Override
	protected SourceVulnerabilityProcessorHelper getSourceVulnerabilityProcessorHelper() {
		return new SSCSourceVulnerabilityProcessorHelperSubmit();
	}
	
	private class SSCSourceVulnerabilityProcessorHelperSubmit extends SourceVulnerabilityProcessorHelperSubmit {
		@Override
		public AbstractRestConnectionQueryBuilder<?, ?> createBaseVulnerabilityQueryBuilder(Context context) {
			SSCApplicationVersionIssuesQueryBuilder builder = createSSCVulnerabilityBaseQueryBuilder(context)
					.paramQm(QueryMode.issues)
					.includeHidden(false)
					.includeRemoved(false)
					.includeSuppressed(false)
					.paramQ(getFullSSCFilterString());
			if ( getVulnerabilityProcessor().isIgnorePreviouslySubmittedIssues() ) {
				builder.preProcessor(new SSCJSONMapFilterHasBugURL(MatchMode.EXCLUDE));
			}
			if ( getConfiguration().isEnableRevisionWorkAround() ) {
				builder.preProcessor(new SSCJSONMapEnrichWithRevisionFromDetails());
			}
			return builder;
		}
		
		/**
		 * Get the full SSC filter string for vulnerabilities that need to be submitted to the bug tracker
		 * @return
		 */
		private String getFullSSCFilterString() {
			String result = getConfiguration().getFilterStringForVulnerabilitiesToBeSubmitted();
			if ( getVulnerabilityProcessor().isIgnorePreviouslySubmittedIssues() && StringUtils.isNotBlank(getConfiguration().getBugLinkCustomTagName()) ) {
				result = StringUtils.isBlank(result) ? "" : (result+" ");
				result += getConfiguration().getBugLinkCustomTagName()+":<none>";
			}
			// SSC doesn't allow filtering on bugURL, so this is handled in createFilterForVulnerabilitiesToBeSubmitted
			return result;
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void updateVulnerabilityStateForNewIssue(Context context, String bugTrackerName, TargetIssueLocatorAndFields targetIssueLocatorAndFields, Collection<Object> vulnerabilities) {
		SSCAuthenticatingRestConnection conn = SSCConnectionFactory.getConnection(context);
		String applicationVersionId = ICLIOptionsSSC.CLI_SSC_APPLICATION_VERSION_ID.getValue(context);
		Map<String,String> customTagValues = getExtraCustomTagValues(context, targetIssueLocatorAndFields, vulnerabilities);
		
		if ( StringUtils.isNotBlank(getConfiguration().getBugLinkCustomTagName()) ) {
			customTagValues.put(getConfiguration().getBugLinkCustomTagName(), targetIssueLocatorAndFields.getLocator().getDeepLink());
		} 
		if ( !customTagValues.isEmpty() ) {
			conn.api(SSCCustomTagAPI.class).setCustomTagValues(applicationVersionId, customTagValues, vulnerabilities);
			LOG.info("[SSC] Updated custom tag values for SSC vulnerabilities");
		}
		if ( getConfiguration().isAddNativeBugLink() ) {
			Map<String, Object> issueDetails = new HashMap<String, Object>();
			issueDetails.put("existingBugLink", targetIssueLocatorAndFields.getLocator().getDeepLink());
			List<String> issueInstanceIds = ContextSpringExpressionUtil.evaluateExpression(context, vulnerabilities, "#root.![issueInstanceId]", List.class);
			
			SSCBugTrackerAPI bugTrackerAPI = conn.api(SSCBugTrackerAPI.class);
			if ( bugTrackerAPI.isBugTrackerAuthenticationRequired(applicationVersionId) ) {
				// If SSC bug tracker username/password are not specified, we use dummy values;
				// 'Add Existing Bugs' doesn't care about credentials but requires authentication
				// to work around SSC 17.20+ bugs
				String btUserName = StringUtils.defaultIfBlank(context.get(ICLIOptionsSSC.PRP_SSC_BUG_TRACKER_USER_NAME, String.class), "dummy");
				String btPassword = StringUtils.defaultIfBlank(context.get(ICLIOptionsSSC.PRP_SSC_BUG_TRACKER_PASSWORD, String.class), "dummy");
				bugTrackerAPI.authenticateForBugFiling(applicationVersionId, btUserName, btPassword);
			}
			
			conn.api(SSCBugTrackerAPI.class).fileBug(applicationVersionId, issueDetails, issueInstanceIds);
			LOG.info("[SSC] Added bug link for SSC vulnerabilities using '"+getConfiguration().getAddNativeBugLinkBugTrackerName()+"' bug tracker");
		}
	}

	
	
}