package com.synopsys.integration.alert.provider.blackduck.collector;

import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.HIGH_VULNERABILITY;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.LOW_VULNERABILITY;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.MEDIUM_VULNERABILITY;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.UNKNOWN_VULNERABILITY;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_BDSA_4;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_1;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_10;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_11;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_12;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_13;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_2;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_3;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_4;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_5;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_6;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_7;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_8;
import static com.synopsys.integration.alert.provider.blackduck.collector.VulnerabilityTestConstants.VULNERABILITY_URL_CVE_9;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.springframework.core.io.ClassPathResource;

import com.google.common.io.Files;
import com.google.gson.Gson;
import com.synopsys.integration.alert.TestConstants;
import com.synopsys.integration.alert.common.message.model.CommonMessageData;
import com.synopsys.integration.alert.common.message.model.ComponentItem;
import com.synopsys.integration.alert.common.message.model.LinkableItem;
import com.synopsys.integration.alert.common.message.model.ProviderMessageContent;
import com.synopsys.integration.alert.common.persistence.accessor.FieldAccessor;
import com.synopsys.integration.alert.common.persistence.model.ConfigurationJobModel;
import com.synopsys.integration.alert.common.util.DateUtils;
import com.synopsys.integration.alert.provider.blackduck.BlackDuckProperties;
import com.synopsys.integration.alert.provider.blackduck.BlackDuckProviderKey;
import com.synopsys.integration.alert.provider.blackduck.collector.builder.BlackDuckIssueTrackerCallbackUtility;
import com.synopsys.integration.alert.provider.blackduck.collector.builder.MessageBuilderConstants;
import com.synopsys.integration.alert.provider.blackduck.collector.builder.VulnerabilityNotificationMessageBuilder;
import com.synopsys.integration.blackduck.api.generated.enumeration.ProjectVersionVulnerableBomComponentsItemsVulnerabilityWithRemediationSeverityType;
import com.synopsys.integration.blackduck.api.generated.view.ComponentVersionView;
import com.synopsys.integration.blackduck.api.generated.view.VulnerabilityView;
import com.synopsys.integration.blackduck.api.manual.view.VulnerabilityNotificationView;
import com.synopsys.integration.blackduck.rest.BlackDuckHttpClient;
import com.synopsys.integration.blackduck.service.BlackDuckService;
import com.synopsys.integration.blackduck.service.BlackDuckServicesFactory;
import com.synopsys.integration.blackduck.service.ComponentService;
import com.synopsys.integration.blackduck.service.ProjectService;
import com.synopsys.integration.blackduck.service.bucket.BlackDuckBucket;
import com.synopsys.integration.blackduck.service.bucket.BlackDuckBucketService;
import com.synopsys.integration.exception.IntegrationException;
import com.synopsys.integration.log.Slf4jIntLogger;

public class VulnerabilityMessageBuilderTest {
    private final Gson gson = new Gson();
    private final BlackDuckIssueTrackerCallbackUtility blackDuckIssueTrackerCallbackUtility = new BlackDuckIssueTrackerCallbackUtility(new BlackDuckProviderKey());

    @Test
    public void testCollectingVulnerability() throws Exception {
        BlackDuckBucket blackDuckBucket = new BlackDuckBucket();
        BlackDuckHttpClient blackDuckHttpClient = BlackDuckMessageBuilderTestHelper.mockHttpClient();
        BlackDuckServicesFactory blackDuckServicesFactory = BlackDuckMessageBuilderTestHelper.mockServicesFactory();
        BlackDuckService blackDuckService = BlackDuckMessageBuilderTestHelper.mockBlackDuckService();
        ProjectService projectService = BlackDuckMessageBuilderTestHelper.mockProjectService();
        ComponentService componentService = Mockito.mock(ComponentService.class);

        Mockito.when(blackDuckServicesFactory.getBlackDuckHttpClient()).thenReturn(blackDuckHttpClient);
        Mockito.when(blackDuckService.getResponse(Mockito.contains("https://a-hub-server.blackduck.com/api/vulnerabilities/"), Mockito.any())).thenThrow(new IntegrationException("Test Integration Exception"));
        Mockito.when(blackDuckServicesFactory.createBlackDuckService()).thenReturn(blackDuckService);
        Mockito.when(blackDuckServicesFactory.createProjectService()).thenReturn(projectService);
        Mockito.when(componentService.getRemediationInformation(Mockito.any(ComponentVersionView.class))).thenReturn(Optional.empty());
        Mockito.when(blackDuckServicesFactory.createComponentService()).thenReturn(componentService);

        VulnerabilityNotificationMessageBuilder vulnMessageBuilder = new VulnerabilityNotificationMessageBuilder(blackDuckIssueTrackerCallbackUtility);
        ConfigurationJobModel job = Mockito.mock(ConfigurationJobModel.class);
        Mockito.when(job.getFieldAccessor()).thenReturn(new FieldAccessor(Map.of()));
        VulnerabilityNotificationView notificationView = getNotificationView(TestConstants.VULNERABILITY_NOTIFICATION_JSON_PATH);
        CommonMessageData commonMessageData = new CommonMessageData(1L, 1L, "provider", "providerConfigName", "providerUrl", DateUtils.createCurrentDateTimestamp(), job);
        List<ProviderMessageContent> providerMessageContents = vulnMessageBuilder.buildMessageContents(commonMessageData, notificationView, blackDuckBucket, blackDuckServicesFactory);

        assertFalse(providerMessageContents.isEmpty());

        List<ComponentItem> componentItems = getComponentItems(providerMessageContents);
        assertComponentItemContent(componentItems, "Custom Component", "1.0.0");
    }

    @Test
    public void testCollectingVulnerabilityWithConnection() throws Exception {
        BlackDuckBucket blackDuckBucket = new BlackDuckBucket();
        BlackDuckHttpClient blackDuckHttpClient = BlackDuckMessageBuilderTestHelper.mockHttpClient();
        BlackDuckServicesFactory blackDuckServicesFactory = BlackDuckMessageBuilderTestHelper.mockServicesFactory();
        BlackDuckService blackDuckService = BlackDuckMessageBuilderTestHelper.mockBlackDuckService();
        ProjectService projectService = BlackDuckMessageBuilderTestHelper.mockProjectService();
        BlackDuckBucketService bucketService = BlackDuckMessageBuilderTestHelper.mockBucketService();
        ComponentService componentService = Mockito.mock(ComponentService.class);

        Mockito.when(blackDuckServicesFactory.getBlackDuckHttpClient()).thenReturn(blackDuckHttpClient);

        Mockito.when(blackDuckServicesFactory.createBlackDuckService()).thenReturn(blackDuckService);
        Mockito.when(blackDuckServicesFactory.createProjectService()).thenReturn(projectService);
        Mockito.when(blackDuckServicesFactory.createBlackDuckBucketService()).thenReturn(bucketService);
        Mockito.when(componentService.getRemediationInformation(Mockito.any(ComponentVersionView.class))).thenReturn(Optional.empty());
        Mockito.when(blackDuckServicesFactory.createComponentService()).thenReturn(componentService);

        VulnerabilityNotificationMessageBuilder vulnMessageBuilder = new VulnerabilityNotificationMessageBuilder(blackDuckIssueTrackerCallbackUtility);
        ConfigurationJobModel job = Mockito.mock(ConfigurationJobModel.class);
        Mockito.when(job.getFieldAccessor()).thenReturn(new FieldAccessor(Map.of()));
        VulnerabilityNotificationView notificationView = getNotificationView(TestConstants.VULNERABILITY_NOTIFICATION_JSON_PATH);
        CommonMessageData commonMessageData = new CommonMessageData(1L, 1L, "provider", "providerConfigName", "providerUrl", DateUtils.createCurrentDateTimestamp(), job);
        List<ProviderMessageContent> providerMessageContents = vulnMessageBuilder.buildMessageContents(commonMessageData, notificationView, blackDuckBucket, blackDuckServicesFactory);

        List<ComponentItem> componentItems = getComponentItems(providerMessageContents);
        assertComponentItemContent(componentItems, "Custom Component", "1.0.0");
    }

    @Test
    public void testCollectingVulnerabilityGetSeverityException() throws Exception {
        BlackDuckBucket blackDuckBucket = new BlackDuckBucket();
        BlackDuckHttpClient blackDuckHttpClient = BlackDuckMessageBuilderTestHelper.mockHttpClient();
        BlackDuckServicesFactory blackDuckServicesFactory = BlackDuckMessageBuilderTestHelper.mockServicesFactory();
        BlackDuckService blackDuckService = BlackDuckMessageBuilderTestHelper.mockBlackDuckService();
        ProjectService projectService = BlackDuckMessageBuilderTestHelper.mockProjectService();
        BlackDuckProperties blackDuckProperties = BlackDuckMessageBuilderTestHelper.mockProperties();
        ComponentService componentService = Mockito.mock(ComponentService.class);

        Mockito.when(blackDuckServicesFactory.getBlackDuckHttpClient()).thenReturn(blackDuckHttpClient);

        Mockito.when(blackDuckService.getResponse(Mockito.contains("https://a-hub-server.blackduck.com/api/vulnerabilities/"), Mockito.any())).thenThrow(new IntegrationException("Test Integration Exception"));
        Mockito.when(blackDuckServicesFactory.createBlackDuckService()).thenReturn(blackDuckService);
        Mockito.when(blackDuckServicesFactory.createProjectService()).thenReturn(projectService);
        Mockito.when(blackDuckProperties.createBlackDuckHttpClientAndLogErrors(Mockito.any(Logger.class))).thenReturn(Optional.of(blackDuckHttpClient));
        Mockito.when(blackDuckProperties.createBlackDuckServicesFactory(Mockito.eq(blackDuckHttpClient), Mockito.any(Slf4jIntLogger.class))).thenReturn(blackDuckServicesFactory);
        Mockito.when(componentService.getRemediationInformation(Mockito.any(ComponentVersionView.class))).thenReturn(Optional.empty());
        Mockito.when(blackDuckServicesFactory.createComponentService()).thenReturn(componentService);

        VulnerabilityNotificationMessageBuilder vulnMessageBuilder = new VulnerabilityNotificationMessageBuilder(blackDuckIssueTrackerCallbackUtility);
        ConfigurationJobModel job = Mockito.mock(ConfigurationJobModel.class);
        Mockito.when(job.getFieldAccessor()).thenReturn(new FieldAccessor(Map.of()));
        VulnerabilityNotificationView notificationView = getNotificationView(TestConstants.VULNERABILITY_NOTIFICATION_JSON_PATH);

        CommonMessageData commonMessageData = new CommonMessageData(1L, 1L, "provider", "providerConfigName", "providerUrl", DateUtils.createCurrentDateTimestamp(), job);
        List<ProviderMessageContent> messageContents = vulnMessageBuilder.buildMessageContents(commonMessageData, notificationView, blackDuckBucket, blackDuckServicesFactory);
        List<ComponentItem> componentItems = getComponentItems(messageContents);
        assertComponentItemContent(componentItems, "Custom Component", "1.0.0");
    }

    @Test
    public void testCollectingVulnerabilityOrdered() throws Exception {
        BlackDuckBucket blackDuckBucket = new BlackDuckBucket();
        BlackDuckHttpClient blackDuckHttpClient = BlackDuckMessageBuilderTestHelper.mockHttpClient();
        BlackDuckServicesFactory blackDuckServicesFactory = BlackDuckMessageBuilderTestHelper.mockServicesFactory();
        BlackDuckService blackDuckService = BlackDuckMessageBuilderTestHelper.mockBlackDuckService();
        ProjectService projectService = BlackDuckMessageBuilderTestHelper.mockProjectService();
        BlackDuckProperties blackDuckProperties = BlackDuckMessageBuilderTestHelper.mockProperties();
        BlackDuckBucketService bucketService = BlackDuckMessageBuilderTestHelper.mockBucketService();
        ComponentService componentService = Mockito.mock(ComponentService.class);

        Mockito.when(blackDuckServicesFactory.getBlackDuckHttpClient()).thenReturn(blackDuckHttpClient);

        VulnerabilityView highVulnerabilityView = new VulnerabilityView();
        highVulnerabilityView.setName("vulnerability");
        highVulnerabilityView.setSeverity(ProjectVersionVulnerableBomComponentsItemsVulnerabilityWithRemediationSeverityType.HIGH);

        VulnerabilityView mediumVulnerabilityView = new VulnerabilityView();
        mediumVulnerabilityView.setName("vulnerability");
        mediumVulnerabilityView.setSeverity(ProjectVersionVulnerableBomComponentsItemsVulnerabilityWithRemediationSeverityType.MEDIUM);

        VulnerabilityView lowVulnerabilityView = new VulnerabilityView();
        lowVulnerabilityView.setName("vulnerability");
        lowVulnerabilityView.setSeverity(ProjectVersionVulnerableBomComponentsItemsVulnerabilityWithRemediationSeverityType.LOW);

        Map<String, VulnerabilityView> urlToSeverityMap = new HashMap<>();
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_1, lowVulnerabilityView);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_2, highVulnerabilityView);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_3, mediumVulnerabilityView);

        Mockito.when(blackDuckServicesFactory.createBlackDuckService()).thenReturn(blackDuckService);
        Mockito.when(blackDuckServicesFactory.createProjectService()).thenReturn(projectService);
        Mockito.when(blackDuckServicesFactory.createBlackDuckBucketService()).thenReturn(bucketService);
        Mockito.when(blackDuckProperties.createBlackDuckHttpClientAndLogErrors(Mockito.any(Logger.class))).thenReturn(Optional.of(blackDuckHttpClient));
        Mockito.when(blackDuckProperties.createBlackDuckServicesFactory(Mockito.eq(blackDuckHttpClient), Mockito.any(Slf4jIntLogger.class))).thenReturn(blackDuckServicesFactory);
        Mockito.when(componentService.getRemediationInformation(Mockito.any(ComponentVersionView.class))).thenReturn(Optional.empty());
        Mockito.when(blackDuckServicesFactory.createComponentService()).thenReturn(componentService);

        VulnerabilityNotificationView notification1 = getNotificationView(TestConstants.VULNERABILITY_NOTIFICATION_SIMPLE_01_JSON_PATH);
        VulnerabilityNotificationView notification2 = getNotificationView(TestConstants.VULNERABILITY_NOTIFICATION_SIMPLE_02_JSON_PATH);
        List<VulnerabilityNotificationView> orderedNotifications = List.of(notification1, notification2);

        VulnerabilityNotificationMessageBuilder vulnMessageBuilder = new VulnerabilityNotificationMessageBuilder(blackDuckIssueTrackerCallbackUtility);

        Long notificationId = 1L;
        ConfigurationJobModel job = Mockito.mock(ConfigurationJobModel.class);
        Mockito.when(job.getFieldAccessor()).thenReturn(new FieldAccessor(Map.of()));
        LinkedList<ProviderMessageContent> providerMessageContents = new LinkedList<>();
        for (VulnerabilityNotificationView notificationView : orderedNotifications) {
            CommonMessageData commonMessageData = new CommonMessageData(notificationId, 1L, "provider", "providerConfigName", "providerUrl", DateUtils.createCurrentDateTimestamp(), job);
            List<ProviderMessageContent> messageContents = vulnMessageBuilder.buildMessageContents(commonMessageData, notificationView, blackDuckBucket, blackDuckServicesFactory);
            providerMessageContents.addAll(messageContents);
            notificationId++;
        }

        for (ProviderMessageContent providerMessageContent : providerMessageContents) {
            System.out.println(providerMessageContent.getSubTopic());
            for (ComponentItem componentItem : providerMessageContent.getComponentItems()) {
                LinkableItem vulnerabilityItem = componentItem.getCategoryItem();
                String vulnerabilityUrl = vulnerabilityItem.getUrl().orElseThrow(() -> new IllegalStateException("Vulnerability URL missing"));

                Optional<LinkableItem> categoryGroupingAttribute = componentItem.getCategoryGroupingAttribute();
                assertFalse(categoryGroupingAttribute.isEmpty(), "The component should have a grouping attribute.");
                LinkableItem groupingAttribute = categoryGroupingAttribute.get();
                if (groupingAttribute.getName().equals(MessageBuilderConstants.LABEL_VULNERABILITY_SEVERITY)) {
                    assertTrue(urlToSeverityMap.containsKey(vulnerabilityUrl));
                    assertEquals(urlToSeverityMap.get(vulnerabilityUrl).getSeverity().name(), groupingAttribute.getValue(), String.format("Failed on component: %s", componentItem.toString()));
                }
            }
        }
    }

    private void assertComponentItemContent(List<ComponentItem> componentItems, String componentName, String componentVersion) {
        Map<String, String> urlToSeverityMap = new HashMap<>();
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_1, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_2, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_3, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_4, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_5, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_6, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_7, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_8, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_9, HIGH_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_10, UNKNOWN_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_11, LOW_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_12, MEDIUM_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_CVE_13, UNKNOWN_VULNERABILITY);
        urlToSeverityMap.put(VULNERABILITY_URL_BDSA_4, LOW_VULNERABILITY);

        assertEquals(45, componentItems.size());
        for (ComponentItem item : componentItems) {
            assertEquals(componentName, item.getComponent().getValue());
            assertEquals(componentVersion, item.getSubComponent().map(LinkableItem::getValue).orElseThrow(() -> new IllegalStateException("Component Version missing")));

            assertTrue(item.getCategoryItem().getUrl().isPresent());
            String vulnerabilityUrl = item.getCategoryItem().getUrl().get();
            String expectedSeverity = urlToSeverityMap.get(vulnerabilityUrl);

            Optional<LinkableItem> severityItem = item.getCategoryGroupingAttribute();
            assertTrue(severityItem.isPresent());
            String actualSeverity = severityItem.get().getValue();
            assertEquals(expectedSeverity, actualSeverity);
        }
    }

    private VulnerabilityNotificationView getNotificationView(String path) throws IOException {
        ClassPathResource classPathResource = new ClassPathResource(path);
        File jsonFile = classPathResource.getFile();
        String notificationString = Files.toString(jsonFile, Charset.defaultCharset());
        return gson.fromJson(notificationString, VulnerabilityNotificationView.class);
    }

    private List<ComponentItem> getComponentItems(List<ProviderMessageContent> providerMessageContents) {
        return providerMessageContents
                   .stream()
                   .map(ProviderMessageContent::getComponentItems)
                   .flatMap(Collection::stream)
                   .collect(Collectors.toList());
    }

}