from . import OBSLocal
import os

# Needed to configure OriginManager
import yaml
from osclib.core import attribute_value_save

# Needed to mock LegalAuto
from osc.core import change_review_state
from unittest.mock import MagicMock

# Import the involved staging commands
from osclib.freeze_command import FreezeCommand
from osclib.select_command import SelectCommand
from osclib.accept_command import AcceptCommand

# Import the involved bots
from check_source import CheckSource
from check_tags_in_requests import TagChecker
legal_auto = __import__("legal-auto")  # Needed because of the dash in the filename
LegalAuto = legal_auto.LegalAuto
origin_manager = __import__("origin-manager")  # Same than above, dash in the filename
OriginManager = origin_manager.OriginManager

PROJECT = 'SUSE:SLE-15-SP3:GA'
DEVEL_PROJECT = 'devel:drinking'
STAGING_PROJECT_NAME = 'SUSE:SLE-15-SP3:GA:Staging:A'
HUMAN_REVIEWER = 'release-manager'
FIXTURES = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fixtures')


class TestSLESubmitRequest(OBSLocal.TestCase):
    """Tests for the whole lifecycle of submit requests in SLE

    Similar in purpose and philosophy to TestFactorySubmitRequest, check
    factory_submit_request_tests.py for more details
    """

    def setUp(self):
        super(TestSLESubmitRequest, self).setUp()

        # Setup the basic scenario, with manual reviewers, staging projects[...]
        self.wf = OBSLocal.SLEWorkflow(PROJECT)
        self.__setup_review_teams()
        self.__setup_devel_package('wine')
        self.__config_origin_manager()

        # Setup the different bots typically used for Factory
        self.setup_review_bot(self.wf, PROJECT, 'factory-auto', CheckSource)
        self.setup_review_bot(self.wf, PROJECT, 'licensedigger', LegalAuto)
        self.setup_review_bot(self.wf, PROJECT, 'sle-changelog-checker', TagChecker)
        self.setup_review_bot(self.wf, PROJECT, 'origin-manager', OriginManager)

        # Simulating the environment to please some of the bots while keeping this test readable
        # may be a bit more tricky than it seems. Check the descriptions of __mock_licendigger and
        # __mock_changelog_checker for more rationale
        self.__mock_licensedigger()
        self.__mock_changelog_checker()

        # The staging project must be frozen in order to move packages into it
        FreezeCommand(self.wf.api).perform(STAGING_PROJECT_NAME)

        # Create the submit request
        self.request = self.wf.create_submit_request(DEVEL_PROJECT, 'wine', add_commit=False)

    def tearDown(self):
        super().tearDown()
        del self.wf

    def project(self):
        return self.wf.projects[PROJECT]

    def test_happy_path(self):
        """Tests the ideal case in which all bots are happy and the request successfully goes
        through staging"""

        reqid = self.request.reqid

        # Initial state: reviews have been created for...
        # ...three human reviewers...
        self.assertReview(reqid, by_group=('sle-release-managers', 'new'))
        self.assertReview(reqid, by_group=('autobuild-team', 'new'))
        self.assertReview(reqid, by_group=('origin-reviewers', 'new'))
        # ...for the staging workflow...
        self.assertReview(reqid, by_group=('sle-staging-managers', 'new'))

        # ...and for the bots.
        # So let's first execute the bots and verify their results
        self.assertReviewBot(reqid, 'factory-auto', 'new', 'accepted')
        self.assertReviewBot(reqid, 'licensedigger', 'new', 'accepted')
        self.assertReviewBot(reqid, 'origin-manager', 'new', 'accepted')
        self.assertReviewBot(reqid, 'sle-changelog-checker', 'new', 'accepted')

        # So now that bots are happy, let's accept the manual reviews
        self.osc_user(HUMAN_REVIEWER)
        change_review_state(self.wf.apiurl, reqid, 'accepted', by_group='sle-release-managers')
        change_review_state(self.wf.apiurl, reqid, 'accepted', by_group='autobuild-team')
        change_review_state(self.wf.apiurl, reqid, 'accepted', by_group='origin-reviewers')
        self.osc_user_pop()

        # Now only the staging workflow is pending
        self.assertReview(reqid, by_group=('sle-release-managers', 'accepted'))
        self.assertReview(reqid, by_group=('autobuild-team', 'accepted'))
        self.assertReview(reqid, by_group=('origin-reviewers', 'accepted'))
        self.assertReview(reqid, by_group=('sle-staging-managers', 'new'))

        # Before using the staging plugin, we need to force a reload of the configuration
        # because assertReviewBot temporarily switches the user and that causes problems
        self.wf.load_config()

        # One staging manager puts the request into the staging project
        SelectCommand(self.wf.api, STAGING_PROJECT_NAME).perform(['wine'])

        # The sle-staging-managers review is now accepted and a new review associated to
        # the staging project has been created
        self.assertReview(reqid, by_group=('sle-staging-managers', 'accepted'))
        self.assertReview(reqid, by_project=(STAGING_PROJECT_NAME, 'new'))

        # Let's say everything looks good in the staging project, so the staging manager can
        # accept that staging
        AcceptCommand(self.wf.api).accept_all([STAGING_PROJECT_NAME], True)

        # Finally, all the reviews are accepted:
        # ...one for each bot,
        self.assertReview(reqid, by_user=('factory-auto', 'accepted'))
        self.assertReview(reqid, by_user=('licensedigger', 'accepted'))
        self.assertReview(reqid, by_user=('sle-changelog-checker', 'accepted'))
        self.assertReview(reqid, by_user=('origin-manager', 'accepted'))
        # ...one for each manual review
        self.assertReview(reqid, by_group=('sle-release-managers', 'accepted'))
        self.assertReview(reqid, by_group=('autobuild-team', 'accepted'))
        self.assertReview(reqid, by_group=('origin-reviewers', 'accepted'))
        # ...and two for the staging project (one as a consequence of selecting the package into a
        # staging project and the other as a consequence of accepting the staging)
        self.assertReview(reqid, by_group=('sle-staging-managers', 'accepted'))
        self.assertReview(reqid, by_project=(STAGING_PROJECT_NAME, 'accepted'))

        # So it's time to accept the request
        self.request.change_state('accepted')
        self.assertRequestState(reqid, name='accepted')

    def __setup_devel_package(self, pkg_name):
        pkg = self.wf.create_package(DEVEL_PROJECT, pkg_name)
        pkg.commit_files(os.path.join(FIXTURES, 'packages', pkg_name))

        target_pkg = OBSLocal.Package(pkg_name, project=self.project(), devel_project=DEVEL_PROJECT)
        target_pkg.create_commit()

    def __setup_review_teams(self):
        """Creates the different review teams for manual reviews.

        For simplicity, this uses a common user for all groups.

        This also sets those groups as reviewers of the target project, to ensure new reviews are
        created for them as soon as the request is created. Note the difference with the Factory
        workflow, in which the groups of human reviewers are not initially set as reviewers in the
        target project configuration. Instead, in Factory the review targeting the human reviewers
        is created by the factory-auto (CheckSource) bot.
        """
        self.wf.create_user(HUMAN_REVIEWER)
        groups = ['sle-release-managers', 'origin-reviewers', 'autobuild-team']
        for group in groups:
            self.wf.create_group(group, users=[HUMAN_REVIEWER])
            self.project().add_reviewers(groups=[group])

    def __config_origin_manager(self):
        """Creates the very minimal configuration needed by origin-manager to work"""
        self.wf.create_attribute_type('OSRT', 'OriginConfig', 1)
        self.wf.remote_config_set({'originmanager-request-age-min': 0})
        config = {
            'origins': [{'<devel>': {}}],
            'review-user': 'origin-manager',
            'fallback-group': 'origin-reviewers'
        }
        config = yaml.dump(config, default_flow_style=False)
        attribute_value_save(self.wf.apiurl, PROJECT, 'OriginConfig', config)

    def __mock_changelog_checker(self):
        """Mocks the verification done by the TagChecker.

        Normally, the bot checks whether the request references an entry in any of the known
        issue trackers or whether the same request has been sent to Factory. Although simulating one
        of those scenarios looks easy (and probably is), the whole check is mocked to return True
        for simplicity (the rest of the execution of the bot still takes place normally).
        """
        bot = self.review_bots['sle-changelog-checker']
        bot.checkTagInRequest = MagicMock(return_value=True)

    def __mock_licensedigger(self):
        """Mocks the execution of the LegalAuto bot, so it always succeeds and accepts the review

        Unfortunatelly, LegalAuto is not written to be testable and it's very dependant on external
        components. So just mocking its whole execution looks like the simplest solution for the
        time being. Hopefully this whole mock could be removed in the future.
        """
        bot = self.review_bots['licensedigger']
        bot.check_requests = MagicMock(side_effect=self.__accept_license)

    def __accept_license(self):
        """See :func:`__mock_licensedigger`"""
        change_review_state(
            apiurl=self.wf.apiurl, reqid=self.request.reqid,
            newstate='accepted', by_user='licensedigger'
        )
