/**
 * blackduck-alert
 *
 * Copyright (c) 2020 Synopsys, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.synopsys.integration.alert.provider.blackduck.collector.builder.util;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;

import com.synopsys.integration.alert.common.message.model.LinkableItem;
import com.synopsys.integration.alert.common.util.DataStructureUtils;
import com.synopsys.integration.alert.provider.blackduck.collector.builder.MessageBuilderConstants;
import com.synopsys.integration.blackduck.api.generated.component.ComponentVersionRemediatingFixesPreviousVulnerabilitiesView;
import com.synopsys.integration.blackduck.api.generated.component.ComponentVersionRiskProfileRiskDataCountsView;
import com.synopsys.integration.blackduck.api.generated.enumeration.ComponentVersionRiskProfileRiskDataCountsCountTypeType;
import com.synopsys.integration.blackduck.api.generated.response.ComponentVersionRemediatingView;
import com.synopsys.integration.blackduck.api.generated.view.ComponentVersionView;
import com.synopsys.integration.blackduck.api.generated.view.ProjectVersionComponentView;
import com.synopsys.integration.blackduck.api.generated.view.ProjectVersionView;
import com.synopsys.integration.blackduck.api.generated.view.RiskProfileView;
import com.synopsys.integration.blackduck.api.generated.view.VulnerabilityView;
import com.synopsys.integration.blackduck.api.manual.throwaway.generated.view.VulnerableComponentView;
import com.synopsys.integration.blackduck.service.BlackDuckService;
import com.synopsys.integration.blackduck.service.ComponentService;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import com.synopsys.integration.exception.IntegrationException;

public final class VulnerabilityUtil {

    private VulnerabilityUtil() {
    }

    public static List<VulnerabilityView> getVulnerabilitiesForComponent(Logger logger, BlackDuckService blackDuckService, String vulnerableComponentVulnerabilitiesURL) {
        try {
            return blackDuckService.getAllResponses(vulnerableComponentVulnerabilitiesURL, VulnerableComponentView.VULNERABILITIES_LINK_RESPONSE.getResponseClass());
        } catch (IntegrationException ex) {
            logger.error("Error getting vulnerabilities ", ex);
        }
        return List.of();
    }

    public static List<VulnerableComponentView> getVulnerableComponentViews(BlackDuckService blackDuckService, ProjectVersionWrapper projectVersionWrapper, ProjectVersionComponentView projectVersionComponentView)
        throws IntegrationException {
        return blackDuckService.getAllResponses(projectVersionWrapper.getProjectVersionView(), ProjectVersionView.VULNERABLE_COMPONENTS_LINK_RESPONSE).stream()
                   .filter(vulnerableComponentView -> vulnerableComponentView.getComponentName().equals(projectVersionComponentView.getComponentName()))
                   .filter(vulnerableComponentView -> vulnerableComponentView.getComponentVersionName().equals(projectVersionComponentView.getComponentVersionName()))
                   .collect(Collectors.toList());
    }

    public static List<LinkableItem> getRemediationItems(ComponentService componentService, ComponentVersionView componentVersionView) throws IntegrationException {
        List<LinkableItem> remediationItems = new LinkedList<>();
        Optional<ComponentVersionRemediatingView> optionalRemediation = componentService.getRemediationInformation(componentVersionView);
        if (optionalRemediation.isPresent()) {
            ComponentVersionRemediatingView remediationOptions = optionalRemediation.get();
            createRemediationItem(remediationOptions::getFixesPreviousVulnerabilities, MessageBuilderConstants.LABEL_REMEDIATION_FIX_PREVIOUS).ifPresent(remediationItems::add);
            createRemediationItem(remediationOptions::getLatestAfterCurrent, MessageBuilderConstants.LABEL_REMEDIATION_LATEST).ifPresent(remediationItems::add);
            createRemediationItem(remediationOptions::getNoVulnerabilities, MessageBuilderConstants.LABEL_REMEDIATION_CLEAN).ifPresent(remediationItems::add);
        }
        return remediationItems;
    }

    public static Map<String, VulnerabilityView> createVulnerabilityViewMap(Logger logger, BlackDuckService blackDuckService, Collection<VulnerableComponentView> vulnerableComponentViews) {
        Set<String> vulnerabilityUrls = new HashSet<>();
        Map<String, VulnerabilityView> vulnerabilityViewMap = new HashMap<>(vulnerableComponentViews.size());
        for (VulnerableComponentView vulnerableComponent : vulnerableComponentViews) {
            // in Black Duck 2019.10.0 the vulnerabilities link was changed to be the href
            Optional<String> href = vulnerableComponent.getHref();

            Optional<String> vulnerabilitiesLink = Optional.empty();
            if (vulnerableComponent.hasLink(VulnerableComponentView.VULNERABILITIES_LINK)) {
                vulnerabilitiesLink = vulnerableComponent.getFirstLink(VulnerableComponentView.VULNERABILITIES_LINK);
            } else if (href.isPresent()) {
                String hrefLink = href.get();
                // check that the href is the vulnerabilities link that we expect
                if (hrefLink.endsWith(VulnerableComponentView.VULNERABILITIES_LINK)) {
                    vulnerabilitiesLink = href;
                }
            }
            if (vulnerabilitiesLink.isPresent()) {
                String vulnerableComponentVulnerabilitiesURL = vulnerabilitiesLink.get();
                if (!vulnerabilityUrls.contains(vulnerableComponentVulnerabilitiesURL)) {
                    Map<String, VulnerabilityView> vulnerabilitiesForComponent = DataStructureUtils.mapToValues(getVulnerabilitiesForComponent(logger, blackDuckService, vulnerableComponentVulnerabilitiesURL), VulnerabilityView::getName);
                    vulnerabilityViewMap.putAll(vulnerabilitiesForComponent);
                    vulnerabilityUrls.add(vulnerabilitiesLink.get());
                }
            } else {
                logger.debug("The {} link could not be found for the vulnerable component '{}' version '{}'.", VulnerableComponentView.VULNERABILITIES_LINK, vulnerableComponent.getComponentName(),
                    vulnerableComponent.getComponentVersionName());
            }
        }
        return vulnerabilityViewMap;
    }

    public static LinkableItem createVulnerabilityAttributeItem(String severityValue, LinkableItem vulnerabilityItem) {
        String capitalizedSeverityValue = StringUtils.capitalize(severityValue.toLowerCase());
        String attributeName = String.format("%s %s", capitalizedSeverityValue, vulnerabilityItem.getName());

        LinkableItem attributeItem = new LinkableItem(attributeName, vulnerabilityItem.getValue(), vulnerabilityItem.getUrl().orElse(null));
        attributeItem.setCollapsible(vulnerabilityItem.isCollapsible());
        return attributeItem;
    }

    private static Optional<LinkableItem> createRemediationItem(Supplier<ComponentVersionRemediatingFixesPreviousVulnerabilitiesView> getRemediationOption, String remediationLabel) {
        ComponentVersionRemediatingFixesPreviousVulnerabilitiesView remediatingVersionView = getRemediationOption.get();
        if (null != remediatingVersionView) {
            String versionText = createRemediationVersionText(remediatingVersionView);
            return Optional.of(new LinkableItem(remediationLabel, versionText, remediatingVersionView.getComponentVersion()));
        }
        return Optional.empty();
    }

    private static String createRemediationVersionText(ComponentVersionRemediatingFixesPreviousVulnerabilitiesView remediatingVersionView) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(remediatingVersionView.getName());
        if (remediatingVersionView.getVulnerabilityCount() != null && remediatingVersionView.getVulnerabilityCount().intValue() > 0) {
            stringBuilder.append(" (Vulnerability Count: ");
            stringBuilder.append(remediatingVersionView.getVulnerabilityCount());
            stringBuilder.append(")");
        }
        return stringBuilder.toString();
    }

    public static boolean doesSecurityRiskProfileHaveVulnerabilities(Logger logger, RiskProfileView securityRiskProfile) {
        logger.debug("Checking if the component still has vulnerabilities...");
        int vulnerabilitiesCount = getSumOfRiskCounts(securityRiskProfile.getCounts());
        logger.debug("Number of vulnerabilities found: {}", vulnerabilitiesCount);
        if (vulnerabilitiesCount > 0) {
            logger.debug("This component still has vulnerabilities");
            return true;
        }
        return false;
    }

    private static int getSumOfRiskCounts(List<ComponentVersionRiskProfileRiskDataCountsView> vulnerabilityCounts) {
        int count = 0;
        for (ComponentVersionRiskProfileRiskDataCountsView riskCount : vulnerabilityCounts) {
            if (!ComponentVersionRiskProfileRiskDataCountsCountTypeType.OK.equals(riskCount.getCountType())) {
                count += riskCount.getCount().intValue();
            }
        }
        return count;
    }
}