import contextlib
import random
import unittest
from io import StringIO
from unittest import skip
from unittest.mock import patch

from requests import Response

import bogrod
from bogrod import Bogrod
from bogrod.contrib import EssentxElementaris
from bogrod.settings import bom_spec_versions
from bogrod.tests import BASE_PATH


class BogrodTests(unittest.TestCase):
    # TODO add more tests
    def test_main(self):
        f = StringIO()
        with contextlib.redirect_stdout(f):
            bogrod.main(['jupyter'])
        output = f.getvalue()
        self.assertIn('bogrod', output)

    def test_contrib_base(self):
        from bogrod.contrib import aggregators
        self.assertIn('dummy', aggregators)

    @skip('functionality currently not working properly')
    def test_from_file(self):
        notes_file = BASE_PATH / 'releasenotes/notes/rc1-99e6a29d3335a383.yaml'
        notes_file_update = BASE_PATH / 'releasenotes/notes/rc1-99e6a29d3335a383-update.yaml'
        sbom = Bogrod.from_sbom(BASE_PATH / 'releasenotes/sbom/jupyter-base-notebook.json')
        sbom.read_notes(notes_file)
        # check previous release with known security issue present
        security_notes = sbom.notes['security']
        self.assertNotIn('CVE-2022-29999 high fixed', security_notes)
        # update notes with current issues
        # -- expected previous issue to be resolved
        # -- expected more issues added
        sbom.update_notes()
        self.assertIn('CVE-2022-29999 high fixed', security_notes)
        self.assertIn('CVE-2021-27478 high open', security_notes)
        sbom.write_notes(notes_file_update)
        buffer = StringIO()
        sbom.report(stream=buffer)
        self.assertTrue(buffer.getvalue().startswith('id'))
        self.assertTrue(buffer.getvalue().__contains__('CVE-2021-27478'))

    def test_cli(self):
        with self.assertRaises(SystemExit):
            bogrod.main(argv='--version'.split(' '))
        bogrod.main(argv=f'{BASE_PATH}/releasenotes/sbom/jupyter-base-notebook.cdx.json'.split(' '))

    def test_filter_issues(self):
        bogrod = Bogrod.from_sbom(BASE_PATH / 'releasenotes/sbom/jupyter-base-notebook.cdx.json')
        # -- no issues expected
        data = bogrod._generate_report_data(issues=True)
        self.assertEqual(len(data), 0)
        # -- disregard issues
        data = bogrod._generate_report_data(issues=False)
        self.assertEqual(len(data), len(bogrod.data['vulnerabilities']))
        # -- simulate an issue
        for vuln in random.sample(bogrod.data['vulnerabilities'], 5):
            vuln_id = vuln['id']
            vex = bogrod.vex.setdefault(vuln_id, {})
            vex_report = vex.setdefault('report', {})
            vex_report['issues'] = [
                {
                    'id': 'CVE-2022-29999',
                    'description': 'this is a test issue'
                }
            ]
        data = bogrod._generate_report_data(issues=True)
        # -- expect at least 5 issues
        # -- we can have more than 5 issues because bogrod.data maintains a list of all issues (in SBOM),
        #    while bogrod.vex maintains a list of CVEs (i.e. the same CVE can be referenced in SBOM multiple times)
        self.assertTrue(len(data) >= 5)
        # -- get 'no issues'
        data = bogrod._generate_report_data(issues=False)
        self.assertTrue(len(data) < len(bogrod.data['vulnerabilities']))
        # -- get 'all issues'
        data = bogrod._generate_report_data(issues=None)
        self.assertEqual(len(data), len(bogrod.data['vulnerabilities']))
        data = bogrod._generate_report_data(issues='*')
        self.assertEqual(len(data), len(bogrod.data['vulnerabilities']))

    def test_pip_audit_cdx(self):
        # test pip audit encoded cyclonedx json
        bogrod = Bogrod.from_sbom(BASE_PATH / 'releasenotes/sbom/python-310.pipaudit.cdx.json')
        data = bogrod._generate_report_data()
        self.assertEqual(len(data), len(bogrod.data['vulnerabilities']))

    def test_fix_metadata_component(self):
        # test adding default metadata
        bogrod = Bogrod.from_sbom(BASE_PATH / 'releasenotes/sbom/python-310.pipaudit.cdx.json')
        self.assertFalse('component' in bogrod.data.get('metadata'))
        bogrod.fix_metadata()
        self.assertTrue('component' in bogrod.data.get('metadata'))
        metadata = bogrod.data.get('metadata')
        self.assertEqual(metadata['component']['name'], 'python-310')
        self.assertEqual(metadata['component']['type'], 'application')

    def test_sbom_schemas(self):
        # when run by tox this tests that the resources/bom-* files have been included
        bogrod = Bogrod.from_sbom(BASE_PATH / 'releasenotes/sbom/jupyter-base-notebook.cdx.json')
        for version in bom_spec_versions.split(','):
            bogrod.data['specVersion'] = version
            schema = bogrod._get_sbom_schema()
            self.assertIn('$schema', schema)

    def test_elementaris_upload_report_url(self):
        """ test urls are appended properly """
        # -- this tests  a common issue with urljoin(base, url) when base does not end with /, or url starts with /
        #    fix: base shall have a trailing slash, url shall have no leading slash
        import json
        aggregator = EssentxElementaris(url='https://sbom.example.com/api/v1')
        mock_response = Response()
        mock_response._content = json.dumps(dict(message='ok')).encode('utf8')
        mock_response.status_code = 200
        projectpath = 'foo/bar'
        sbompath = BASE_PATH / 'releasenotes/sbom/jupyter-base-notebook.cdx.json'
        # -- get sbom tentative report
        with patch('requests.post') as mock_post:
            mock_post.return_value = mock_response
            aggregator.upload_sbom(projectpath, sbompath, tentative=True)
            mock_post.assert_called_once()
            self.assertEqual(mock_post.call_args[0][0], 'https://sbom.example.com/api/v1/sbom/temporary-report')
        # -- submit sbom
        with patch('requests.post') as mock_post:
            mock_post.return_value = mock_response
            aggregator.upload_sbom(projectpath, sbompath, tentative=False)
            mock_post.assert_called_once()
            self.assertEqual(mock_post.call_args[0][0], 'https://sbom.example.com/api/v1/sbom')
        # -- get final report
        with patch('requests.get') as mock_get:
            mock_get.return_value = mock_response
            aggregator.get_report('someid')
            self.assertEqual(mock_get.call_args[0][0], 'https://sbom.example.com/api/v1/sbom/reports/someid')


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