"""
SPDX-License-Identifier: MIT

  Copyright (c) 2024, SCANOSS

  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.
"""

import json
import os
import re
import unittest
from unittest.mock import Mock, patch

from src.scanoss.inspection.policy_check import PolicyStatus
from src.scanoss.inspection.raw.component_summary import ComponentSummary
from src.scanoss.inspection.raw.copyleft import Copyleft
from src.scanoss.inspection.raw.license_summary import LicenseSummary
from src.scanoss.inspection.raw.undeclared_component import UndeclaredComponent
from src.scanoss.inspection.dependency_track.project_violation import DependencyTrackProjectViolationPolicyCheck


class MyTestCase(unittest.TestCase):
    """
    Inspect for copyleft licenses
    """

    def test_copyleft_policy(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='json')
        copyleft.run()
        self.assertEqual(True, True)

    """
       Inspect for copyleft licenses empty path
    """

    def test_copyleft_policy_empty_path(self):
        copyleft = Copyleft(filepath='', format_type='json')
        success, results = copyleft.run()
        self.assertTrue(success, 2)

    """
    Inspect for empty copyleft licenses
    """

    def test_empty_copyleft_policy(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result-no-copyleft.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='json')
        status, data = copyleft.run()
        details = json.loads(data['details'])
        self.assertEqual(status, PolicyStatus.POLICY_SUCCESS.value)
        self.assertEqual(details, {})
        self.assertEqual(data['summary'], '0 component(s) with copyleft licenses were found.\n')

    """
    Inspect for copyleft licenses include
    """

    def test_copyleft_policy_include(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='json', include='MIT')
        status, data = copyleft.run()
        has_mit_license = False
        details = json.loads(data['details'])
        for component in details['components']:
            for license in component['licenses']:
                if license['spdxid'] == 'MIT':
                    has_mit_license = True
                    break

        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)
        self.assertEqual(has_mit_license, True)

    """
       Inspect for copyleft licenses exclude
    """

    def test_copyleft_policy_exclude(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='json', exclude='GPL-2.0-only')
        status, data = copyleft.run()
        results = json.loads(data['details'])
        self.assertEqual(results, {})
        self.assertEqual(status, PolicyStatus.POLICY_SUCCESS.value)

    """
        Inspect for copyleft licenses explicit
    """

    def test_copyleft_policy_explicit(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='json', explicit='MIT')
        status, data = copyleft.run()
        results = json.loads(data['details'])
        self.assertEqual(len(results['components']), 2)
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)

    """
        Inspect for copyleft licenses empty explicit licenses (should set the default ones)
    """

    def test_copyleft_policy_empty_explicit(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='json', explicit='')
        status, data = copyleft.run()
        results = json.loads(data['details'])
        self.assertEqual(len(results['components']), 5)
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)

    """
        Export copyleft licenses in Markdown
    """

    def test_copyleft_policy_markdown(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='md', explicit='MIT')
        status, data = copyleft.run()
        expected_detail_output = (
            '### Copyleft Licenses \n  | Component | License | URL | Copyleft |\n'
            ' | - | :-: | - | - |\n'
            ' | pkg:npm/%40electron/rebuild | MIT | https://spdx.org/licenses/MIT.html | YES |\n'
            '| pkg:npm/%40emotion/react | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
        )
        expected_summary_output = '2 component(s) with copyleft licenses were found.\n'
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', data['details']),
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_detail_output),
        )
        self.assertEqual(data['summary'], expected_summary_output)
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)

    ## Undeclared Components Policy Tests ##

    """
       Inspect for undeclared components empty path
    """

    def test_undeclared_policy_empty_path(self):
        undeclared = UndeclaredComponent(filepath='', format_type='json')
        success, results = undeclared.run()
        self.assertTrue(success, 2)

    """
    Inspect for undeclared components
    """

    def test_undeclared_policy(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        undeclared = UndeclaredComponent(filepath=input_file_name, format_type='json', sbom_format='legacy')
        status, data = undeclared.run()
        results = json.loads(data['details'])
        summary = data['summary']
        expected_summary_output = """3 undeclared component(s) were found.
        Add the following snippet into your `sbom.json` file 
        ```json 
        {
            "components":[
                  {
                    "purl": "pkg:github/scanoss/jenkins-pipeline-example"
                  },
                  {
                    "purl": "pkg:github/scanoss/scanner.c"
                  },
                  {
                    "purl": "pkg:github/scanoss/wfp"
                  }
            ]
        }```
        """
        self.assertEqual(len(results['components']), 4)
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
        )
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)

    """
       Undeclared component markdown output
    """

    def test_undeclared_policy_markdown(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        undeclared = UndeclaredComponent(filepath=input_file_name, format_type='md', sbom_format='legacy')
        status, data = undeclared.run()
        results = data['details']
        summary = data['summary']
        expected_details_output = """ ### Undeclared components
             | Component | License | 
             | - | - | 
             | pkg:github/scanoss/jenkins-pipeline-example | unknown | 
             | pkg:github/scanoss/scanner.c | GPL-2.0-only | 
             | pkg:github/scanoss/wfp | GPL-2.0-only |  """

        expected_summary_output = """3 undeclared component(s) were found.
           Add the following snippet into your `sbom.json` file 
           ```json 
               {
                "components":[
                 {
                    "purl": "pkg:github/scanoss/jenkins-pipeline-example"
                 },
                 {
                    "purl": "pkg:github/scanoss/scanner.c"
                  },
                  {
                    "purl": "pkg:github/scanoss/wfp"
                  }         
                ]             
               }```
           """
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', results), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_details_output)
        )
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
        )

    """
         Undeclared component markdown scanoss summary output
    """

    def test_undeclared_policy_markdown_scanoss_summary(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        undeclared = UndeclaredComponent(filepath=input_file_name, format_type='md')
        status, data = undeclared.run()
        results = data['details']
        summary = data['summary']
        expected_details_output = """ ### Undeclared components
               | Component | License | 
               | - | - | 
               | pkg:github/scanoss/jenkins-pipeline-example | unknown |
               | pkg:github/scanoss/scanner.c | GPL-2.0-only | 
               | pkg:github/scanoss/wfp | GPL-2.0-only | """

        expected_summary_output = """3 undeclared component(s) were found.
            Add the following snippet into your `scanoss.json` file
            
            ```json
            {
              "bom": {
                "include": [
                  {
                    "purl": "pkg:github/scanoss/jenkins-pipeline-example"
                  },
                  {
                    "purl": "pkg:github/scanoss/scanner.c"
                  },
                  {
                    "purl": "pkg:github/scanoss/wfp"
                  }
                ]
              }
            }
            ```"""
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', results), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_details_output)
        )
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
        )

    """
        Undeclared component sbom summary output
    """

    def test_undeclared_policy_scanoss_summary(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        undeclared = UndeclaredComponent(filepath=input_file_name)
        status, data = undeclared.run()
        results = json.loads(data['details'])
        summary = data['summary']
        expected_summary_output = """3 undeclared component(s) were found.
                Add the following snippet into your `scanoss.json` file

                ```json
                {
                  "bom": {
                    "include": [
                      {
                        "purl": "pkg:github/scanoss/jenkins-pipeline-example"
                      },
                      {
                        "purl": "pkg:github/scanoss/scanner.c"
                      },
                      {
                        "purl": "pkg:github/scanoss/wfp"
                      }
                    ]
                  }
                }
                ```"""
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)
        self.assertEqual(len(results['components']), 4)
        self.assertEqual(
            re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
        )

    def test_undeclared_policy_jira_markdown_output(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        undeclared = UndeclaredComponent(filepath=input_file_name, format_type='jira_md')
        status, data = undeclared.run()
        details = data['details']
        summary = data['summary']
        expected_details_output = """|*Component*|*License*|
|pkg:github/scanoss/jenkins-pipeline-example|unknown|
|pkg:github/scanoss/scanner.c|GPL-2.0-only|
|pkg:github/scanoss/wfp|GPL-2.0-only|
"""
        expected_summary_output = """3 undeclared component(s) were found.
Add the following snippet into your `scanoss.json` file
{code:json}
{
  "bom": {
    "include": [
      {
        "purl": "pkg:github/scanoss/jenkins-pipeline-example"
      },
      {
        "purl": "pkg:github/scanoss/scanner.c"
      },
      {
        "purl": "pkg:github/scanoss/wfp"
      }
    ]
  }
}
{code}
"""
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)
        self.assertEqual(expected_details_output, details)
        self.assertEqual(summary, expected_summary_output)

    def test_copyleft_policy_jira_markdown_output(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        copyleft = Copyleft(filepath=input_file_name, format_type='jira_md')
        status, data = copyleft.run()
        results = data['details']
        expected_details_output = """### Copyleft Licenses\n|*Component*|*License*|*URL*|*Copyleft*|
|pkg:github/scanoss/scanner.c|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|pkg:github/scanoss/engine|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|pkg:github/scanoss/wfp|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
"""
        self.assertEqual(status, PolicyStatus.POLICY_FAIL.value)
        self.assertEqual(expected_details_output, results)

    def test_inspect_license_summary(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        i_license_summary = LicenseSummary(filepath=input_file_name)
        license_summary = i_license_summary.run()
        self.assertEqual(license_summary['detectedLicenses'], 3)
        self.assertEqual(license_summary['detectedLicensesWithCopyleft'], 1)

    def test_inspect_license_summary_with_empty_result(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'empty-result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        i_license_summary = LicenseSummary(filepath=input_file_name)
        license_summary = i_license_summary.run()
        self.assertEqual(license_summary['detectedLicenses'], 0)
        self.assertEqual(license_summary['detectedLicensesWithCopyleft'], 0)
        self.assertEqual(len(license_summary['licenses']), 0)

    def test_inspect_component_summary(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        i_component_summary = ComponentSummary(filepath=input_file_name)
        component_summary = i_component_summary.run()
        print(component_summary)
        self.assertEqual(component_summary['totalComponents'], 4)
        self.assertEqual(component_summary['undeclaredComponents'], 3)
        self.assertEqual(component_summary['declaredComponents'], 1)
        self.assertEqual(component_summary['totalFilesDetected'], 10)
        self.assertEqual(component_summary['totalFilesUndeclared'], 8)
        self.assertEqual(component_summary['totalFilesDeclared'], 2)

    def test_inspect_component_summary_empty_result(self):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        file_name = 'empty-result.json'
        input_file_name = os.path.join(script_dir, 'data', file_name)
        i_component_summary = ComponentSummary(filepath=input_file_name)
        component_summary = i_component_summary.run()
        self.assertEqual(component_summary['totalComponents'], 0)
        self.assertEqual(component_summary['undeclaredComponents'], 0)
        self.assertEqual(component_summary['declaredComponents'], 0)
        self.assertEqual(len(component_summary['components']), 0)
        self.assertEqual(component_summary['totalFilesDetected'], 0)
        self.assertEqual(component_summary['totalFilesUndeclared'], 0)
        self.assertEqual(component_summary['totalFilesDeclared'], 0)

    ## Dependency Track Project Violation Policy Tests ##

    @patch('src.scanoss.inspection.dependency_track.project_violation.DependencyTrackService')
    def test_dependency_track_project_violation_json_formatter(self, mock_service):
        mock_service.return_value = Mock()
        project_violation = DependencyTrackProjectViolationPolicyCheck(
            format_type='json',
            api_key='test_key',
            url='http://localhost',
            project_id='test_project'
        )
        test_violations = [
            {
                'uuid': 'violation-1',
                'type': 'SECURITY',
                'timestamp': 1640995200000,
                'component': {
                    'name': 'test-component',
                    'version': '1.0.0',
                    'purl': 'pkg:npm/test-component@1.0.0'
                },
                'policyCondition': {
                    'policy': {
                        'name': 'Security Policy',
                        'violationState': 'FAIL'
                    }
                }
            }
        ]
        result = project_violation._json(test_violations)
        self.assertIn('details', result)
        self.assertIn('summary', result)
        self.assertEqual(result['summary'], '1 policy violations were found.\n')
        details = json.loads(result['details'])
        self.assertEqual(len(details), 1)
        self.assertEqual(details[0]['type'], 'SECURITY')

    @patch('src.scanoss.inspection.dependency_track.project_violation.DependencyTrackService')
    def test_dependency_track_project_violation_markdown_formatter(self, mock_service):
        mock_service.return_value = Mock()
        project_violation = DependencyTrackProjectViolationPolicyCheck(
            format_type='md',
            api_key='test_key',
            url='http://localhost',
            project_id='test_project'
        )
        test_violations = [
            {
                'uuid': 'violation-1',
                'type': 'SECURITY',
                'timestamp': 1640995200000,
                'component': {
                    'name': 'test-component',
                    'version': '1.0.0',
                    'purl': 'pkg:npm/test-component@1.0.0'
                },
                'policyCondition': {
                    'policy': {
                        'name': 'Security Policy',
                        'violationState': 'FAIL'
                    }
                }
            }
        ]
        result = project_violation._markdown(test_violations)
        self.assertIn('details', result)
        self.assertIn('summary', result)
        self.assertEqual(result['summary'], '1 policy violations were found.\n')
        self.assertIn('State', result['details'])
        self.assertIn('Risk Type', result['details'])
        self.assertIn('Policy Name', result['details'])
        self.assertIn('Component', result['details'])
        self.assertIn('Date', result['details'])

    @patch('src.scanoss.inspection.dependency_track.project_violation.DependencyTrackService')
    def test_dependency_track_project_violation_sort_violations(self, mock_service):
        mock_service.return_value = Mock()
        project_violation = DependencyTrackProjectViolationPolicyCheck(
            api_key='test_key',
            url='http://localhost',
            project_id='test_project'
        )
        test_violations = [
            {'type': 'LICENSE', 'uuid': 'license-violation'},
            {'type': 'SECURITY', 'uuid': 'security-violation'},
            {'type': 'OTHER', 'uuid': 'other-violation'},
            {'type': 'SECURITY', 'uuid': 'security-violation-2'}
        ]
        sorted_violations = project_violation._sort_project_violations(test_violations)
        self.assertEqual(sorted_violations[0]['type'], 'SECURITY')
        self.assertEqual(sorted_violations[1]['type'], 'SECURITY')
        self.assertEqual(sorted_violations[2]['type'], 'LICENSE')
        self.assertEqual(sorted_violations[3]['type'], 'OTHER')

    @patch('src.scanoss.inspection.dependency_track.project_violation.DependencyTrackService')
    def test_dependency_track_project_violation_empty_violations(self, mock_service):
        mock_service.return_value = Mock()
        project_violation = DependencyTrackProjectViolationPolicyCheck(
            format_type='json',
            api_key='test_key',
            url='http://localhost',
            project_id='test_project'
        )
        empty_violations = []
        result = project_violation._json(empty_violations)
        self.assertEqual(result['summary'], '0 policy violations were found.\n')
        details = json.loads(result['details'])
        self.assertEqual(len(details), 0)

    @patch('src.scanoss.inspection.dependency_track.project_violation.DependencyTrackService')
    def test_dependency_track_project_violation_markdown_empty(self, mock_service):
        mock_service.return_value = Mock()
        project_violation = DependencyTrackProjectViolationPolicyCheck(
            format_type='md',
            api_key='test_key',
            url='http://localhost',
            project_id='test_project'
        )
        empty_violations = []
        result = project_violation._markdown(empty_violations)
        self.assertEqual(result['summary'], '0 policy violations were found.\n')
        self.assertIn('State', result['details'])
        self.assertIn('Risk Type', result['details'])

    @patch('src.scanoss.inspection.dependency_track.project_violation.DependencyTrackService')
    def test_dependency_track_project_violation_multiple_types(self, mock_service):
        mock_service.return_value = Mock()
        project_violation = DependencyTrackProjectViolationPolicyCheck(
            format_type='json',
            api_key='test_key',
            url='http://localhost',
            project_id='test_project'
        )
        test_violations = [
            {
                'uuid': 'violation-1',
                'type': 'SECURITY',
                'timestamp': 1640995200000,
                'component': {
                    'name': 'vulnerable-component',
                    'version': '1.0.0',
                    'purl': 'pkg:npm/vulnerable-component@1.0.0'
                },
                'policyCondition': {
                    'policy': {
                        'name': 'Security Policy',
                        'violationState': 'FAIL'
                    }
                }
            },
            {
                'uuid': 'violation-2',
                'type': 'LICENSE',
                'timestamp': 1640995300000,
                'component': {
                    'name': 'license-component',
                    'version': '2.0.0',
                    'purl': 'pkg:npm/license-component@2.0.0'
                },
                'policyCondition': {
                    'policy': {
                        'name': 'License Policy',
                        'violationState': 'WARN'
                    }
                }
            }
        ]
        result = project_violation._json(test_violations)
        self.assertEqual(result['summary'], '2 policy violations were found.\n')
        details = json.loads(result['details'])
        self.assertEqual(len(details), 2)

if __name__ == '__main__':
    unittest.main()
