#
# -------------------------------------------------------------------------
#
#  Part of the CodeChecker project, under the Apache License v2.0 with
#  LLVM Exceptions. See LICENSE for license information.
#  SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
""" Run tag function test. """


import json
import os
import shutil
import unittest

from codechecker_api.codeCheckerDBAccess_v6.ttypes import Order, \
    ReportFilter, RunSortMode, RunSortType

from libtest import codechecker
from libtest import env


class TestRunTag(unittest.TestCase):

    def setup_class(self):
        """Setup the environment for testing detection_status."""

        global TEST_WORKSPACE
        TEST_WORKSPACE = env.get_workspace('run_tag')

        # Set the TEST_WORKSPACE used by the tests.
        os.environ['TEST_WORKSPACE'] = TEST_WORKSPACE

        # Configuration options.
        codechecker_cfg = {
            'suppress_file': None,
            'skip_list_file': None,
            'check_env': env.test_env(TEST_WORKSPACE),
            'workspace': TEST_WORKSPACE,
            'checkers': [],
            'reportdir': os.path.join(TEST_WORKSPACE, 'reports'),
            'test_project': 'test_run_tag',
            'analyzers': ['clangsa', 'clang-tidy']
        }

        # Start or connect to the running CodeChecker server and get connection
        # details.
        print("This test uses a CodeChecker server... connecting...")
        server_access = codechecker.start_or_get_server()
        server_access['viewer_product'] = 'run_tag'
        codechecker.add_test_package_product(server_access, TEST_WORKSPACE)

        # Extend the checker configuration with the server access.
        codechecker_cfg.update(server_access)

        # Export the test configuration to the workspace.
        env.export_test_cfg(TEST_WORKSPACE,
                            {'codechecker_cfg': codechecker_cfg})

    def teardown_class(self):
        """Clean up after the test."""

        # TODO: If environment variable is set keep the workspace
        # and print out the path.
        global TEST_WORKSPACE

        check_env = env.import_test_cfg(TEST_WORKSPACE)[
            'codechecker_cfg']['check_env']
        codechecker.remove_test_package_product(TEST_WORKSPACE, check_env)

        print("Removing: " + TEST_WORKSPACE)
        shutil.rmtree(TEST_WORKSPACE, ignore_errors=True)

    def setup_method(self, _):
        # TEST_WORKSPACE is automatically set by test package __init__.py .
        self.test_workspace = os.environ['TEST_WORKSPACE']

        test_class = self.__class__.__name__
        print('Running ' + test_class + ' tests in ' + self.test_workspace)

        self._codechecker_cfg = env.import_codechecker_cfg(self.test_workspace)
        self._test_dir = os.path.join(self.test_workspace, 'test_files')

        try:
            os.makedirs(self._test_dir)
        except os.error:
            # Directory already exists.
            pass

        # Setup a viewer client to test viewer API calls.
        self._cc_client = env.setup_viewer_client(self.test_workspace)
        self.assertIsNotNone(self._cc_client)

        # Change working dir to testfile dir so CodeChecker can be run easily.
        self.__old_pwd = os.getcwd()
        os.chdir(self._test_dir)

        self._source_file = "main.cpp"

        # Init project dir.
        makefile = "all:\n\t$(CXX) -c main.cpp -o /dev/null\n"
        project_info = {
            "name": "test_run_tag",
            "clean_cmd": "",
            "build_cmd": "make"
        }

        with open(os.path.join(self._test_dir, 'Makefile'), 'w',
                  encoding="utf-8", errors="ignore") as f:
            f.write(makefile)
        with open(os.path.join(self._test_dir, 'project_info.json'), 'w',
                  encoding="utf-8", errors="ignore") as f:
            json.dump(project_info, f)

        self.sources = ["""
int main()
{
  sizeof(42);
  sizeof(43);
}""", """
int main()
{
  sizeof(43);
  sizeof(44);
  sizeof(45);
}""", """
int main()
{
  sizeof(45);
}"""]
        self.tags = ['v1.0', 'v1.1', 'v1.2']

    def teardown_method(self, _):
        """Restore environment after tests have ran."""
        os.chdir(self.__old_pwd)

    def _create_source_file(self, version, run_name):
        with open(os.path.join(self._test_dir, self._source_file), 'w',
                  encoding="utf-8", errors="ignore") as f:
            f.write(self.sources[version])

        self._codechecker_cfg['tag'] = self.tags[version]
        codechecker.check_and_store(self._codechecker_cfg,
                                    run_name, self._test_dir)

    def __get_run_tag_counts(self, run_id, limit=None, offset=0):
        """
        Get run history tag for the given run.
        """
        test_run_tags = self._cc_client.getRunHistoryTagCounts(
            [run_id], ReportFilter(), None, limit, offset)

        self.assertGreater(len(test_run_tags), 0)

        return test_run_tags

    def __reports_by_tag(self, tag_ids):
        report_filter = ReportFilter(runTag=tag_ids)
        return self._cc_client.getRunResults(None, 100, 0, [],
                                             report_filter, None, False)

    def __check_reports(self, tag):
        reports = self.__reports_by_tag([tag.id])
        self.assertEqual(tag.count, len(reports))

        for report in reports:
            self.assertLessEqual(report.detectedAt, tag.time)

    def test_file_change(self):
        """
        This tests the change of the detection status of bugs when the file
        content changes.

        CHECK 1 ----- CHECK 2 --------- CHECK 3 ---------- CHECK 4
           |             |                 |                  |
          42             |                 |                  |
           |             |                 |                  |
          43             |                 |                  |
                         |                 |                  |
                        42 ---------- [RESOLVED]              |
                         |                 |                  |
                        43 --------- [UNRESOLVED] ------- [RESOLVED]
                                           |                  |
                                          44 ------------ [RESOLVED]
                                           |                  |
                                          45 ----------- [UNRESOLVED]
        """

        # ######################## 1st check #################################

        # Check the first file version.
        self._create_source_file(0, 'test_run_tag')

        # ######################## 2nd check #################################

        # Check the first file versions again with different run name to see
        # that set a run tag filter will filter the reports by the run id.
        self._create_source_file(0, 'test_run_tag_update')

        # Get the run names which belong to this test

        sort_mode = RunSortMode(RunSortType.DATE, Order.ASC)
        runs = self._cc_client.getRunData(None, None, 0, sort_mode)
        test_run_ids = [run.runId for run in runs]

        self.assertEqual(len(test_run_ids), 2)

        run_id = test_run_ids[1]
        test_run_tags = self.__get_run_tag_counts(run_id, 100, 0)
        run_tags_v0 = [t for t in test_run_tags if t.name == self.tags[0]]

        self.assertEqual(len(run_tags_v0), 1)
        run_tags_v0 = run_tags_v0[0]

        reports = self.__reports_by_tag([run_tags_v0.id])
        self.assertEqual(run_tags_v0.count, len(reports))

        # ######################## 3rd check #################################

        # Check the second file version.
        self._create_source_file(1, 'test_run_tag_update')

        # We do not show future bugs for later tags.
        reports = self.__reports_by_tag([run_tags_v0.id])
        self.assertEqual(run_tags_v0.count, len(reports))

        test_run_tags = self.__get_run_tag_counts(run_id)
        run_tags_v1 = [t for t in test_run_tags if t.name == self.tags[1]]

        self.assertEqual(len(run_tags_v1), 1)
        run_tags_v1 = run_tags_v1[0]
        self.__check_reports(run_tags_v1)

        # ######################## 4th check #################################

        # Check the third file version.
        self._create_source_file(2, 'test_run_tag_update')

        # We do not show future bugs for later tags.
        reports = self.__reports_by_tag([run_tags_v0.id])
        self.assertEqual(run_tags_v0.count, len(reports))

        test_run_tags = self.__get_run_tag_counts(run_id)
        run_tags_v2 = [t for t in test_run_tags if t.name == self.tags[2]]

        self.assertEqual(len(run_tags_v2), 1)
        run_tags_v2 = run_tags_v2[0]
        self.__check_reports(run_tags_v2)
