from mss import mss

from reportportal_behave.entities.feature import Feature
from reportportal_behave.entities.scenario import Scenario
from reportportal_behave.entities.step import Step
from reportportal_behave.reportportal_service import *


class BehaveIntegrationService:

    def __init__(self,
                 rp_endpoint,
                 rp_project,
                 rp_token,
                 rp_launch_name,
                 rp_launch_description,
                 rp_enable=True,
                 step_based=False,
                 add_screenshot=False,
                 verify_ssl=False):
        self.rp_endpoint = rp_endpoint
        self.rp_project = rp_project
        self.rp_token = rp_token
        self.rp_launch_name = rp_launch_name
        self.rp_launch_description = rp_launch_description
        self.rp_enable = rp_enable
        self.step_based = step_based
        self.add_screenshot = add_screenshot
        self.service = IntegrationService(rp_endpoint=rp_endpoint,
                                          rp_project=rp_project,
                                          rp_token=rp_token,
                                          rp_launch_name=rp_launch_name,
                                          rp_launch_description=rp_launch_description,
                                          verify_ssl=verify_ssl)

    def log_step_error_result(self, step_name, item_id, error_msg=None):
        """
        Log errors in steps if it happens
        :param step_name: the name of the step
        :param item_id: the id of the step execution
        :param error_msg: the error message
        :return: None
        """
        if self.add_screenshot:
            with mss() as sct:
                screenshot_name = sct.shot()

            # Logs assertion message with attachment and ERROR level it step was failed.
            self.service.log_step_result(end_time=timestamp(),
                                         message=error_msg,
                                         level='ERROR',
                                         attachment={
                                             'name': step_name,
                                             'data': open(screenshot_name, 'rb'),
                                             'mime': 'application/octet-stream'
                                         },
                                         item_id=item_id
                                         )
        else:
            self.service.log_step_result(end_time=timestamp(),
                                         message=error_msg,
                                         level='ERROR',
                                         item_id=item_id)

    def launch_service(self, tags, attributes):
        """
        Start the service that will communicate results to ReportPortal
        :param tags: the tags the test execution was triggered with
        :return: the id of the launch required to mark it complete
        """
        if self.rp_enable:
            return self.service.start_launcher(name=self.rp_launch_name,
                                               start_time=timestamp(),
                                               tags=tags,
                                               attributes=attributes,                                               
                                               description=self.rp_launch_description
                                               )

    def before_feature(self, feature):
        """
        Log the start of a feature execution
        :param feature: the feature passed from behave
        :return: the id of the feature execution required to mark it complete
        """
        if self.rp_enable:
            feature_info = Feature(feature)
            return self.service.start_feature_test(name=feature_info.name,
                                                   description=feature_info.description,
                                                   tags=feature_info.tags,
                                                   start_time=timestamp(),
                                                   item_type=feature_info.item_type,
                                                   parent_item_id=None)

    def before_scenario(self, scenario, feature_id):
        """
        Log the start of a scenario execution
        :param scenario: the behave scenario
        :param feature_id: the id of the feature execution
        :return: the id of te scenario execution required to mark it complete
        """
        if self.rp_enable:
            scenario_info = Scenario(scenario, feature_id=feature_id)
            return self.service.start_scenario_test(name=scenario_info.name,
                                                    description=scenario_info.description,
                                                    tags=scenario_info.tags,
                                                    start_time=timestamp(),
                                                    item_type=scenario_info.item_type,
                                                    parent_item_id=scenario_info.feature_id)

    def before_step(self, step, scenario_id):
        """
        Logs the start of a step execution.
        This method is ran only if step_based logging is enabled
        :param step: the behave step
        :param scenario_id: the id of the scenario execution the step belongs to
        :return: the id of the step execution required to mark it complete
        """
        if self.rp_enable and self.step_based:
            step_info = Step(step, scenario_id=scenario_id)

            return self.service.start_step_test(name=f"{step_info.keyword} {step_info.name}",
                                                start_time=timestamp(),
                                                item_type=step_info.item_type,
                                                description=step_info.description,
                                                parent_item_id=step_info.scenario_id)

    def after_step(self, step, step_id):
        """
        Mark the step as complete and set the status for it
        :param step: the behave step
        :param step_id: the id of the step execution received from the before_step
        :return: response of the step completion if available
        """
        step_info = Step(step, step_id=step_id)
        if self.rp_enable and self.step_based:
            # Finishes step
            if step.status == 'failed':
                # Takes a demo screenshot of desktop (can be removed)

                self.log_step_error_result(error_msg=f"{step.exception}",
                                           step_name=step_info.name,
                                           item_id=step_info.step_id)
                return self.service.finish_step_test(end_time=timestamp(),
                                                     status='FAILED',
                                                     item_id=step_info.step_id)
            else:
                return self.service.finish_step_test(end_time=timestamp(),
                                                     status='PASSED',
                                                     item_id=step_info.step_id)

        # Else SCENARIO based logging will be used
        elif self.rp_enable:
            # Creates text log message with INFO level.
            if step.table:
                message = '%s %s\n~~~~~~~~~~~~~~~~~~~~~~~~~\nStep data table:\n%s' % (
                    step_info.keyword,
                    step_info.name,
                    step_info.description)
            elif step.text:
                message = '%s %s\n~~~~~~~~~~~~~~~~~~~~~~~~~\nStep data text:\n%s' % (
                    step_info.keyword,
                    step_info.name,
                    step_info.description)
            else:
                message = step_info.description
            self.service.log_step_result(end_time=timestamp(),
                                         message=message,
                                         level='INFO',
                                         item_id=step_info.step_id)

            if step.status == 'failed':
                # Logs assertion message with attachment and ERROR level it step was failed.
                self.log_step_error_result(error_msg=f"{step.exception}",
                                           step_name=step_info.name,
                                           item_id=step_info.step_id)

    def after_scenario(self, scenario, scenario_id):
        """
        Mark scenario as complete and set the status accordingly
        :param scenario: the behave scenario
        :param scenario_id: the id of the scenario execution received from before scenario
        :return: response of the scenario completion if available
        """
        if self.rp_enable:
            scenario_info = Scenario(scenario, scenario_id=scenario_id)
            for step in scenario.steps:
                if step.status.name == 'undefined':
                    # Logs scenario if it is undefined
                    self.service.log_step_result(end_time=timestamp(),
                                                 message='%s %s - step is undefined.\n' % (
                                                     step.keyword, step.name),
                                                 level='WARN',
                                                 item_id=scenario_info.scenario_id)
                    # Logs scenario as skipped if it is marked for skip
                    # For example scenario marked by @skip tag and --tags=~@skip specified in params of run
                elif step.status.name == 'skipped':
                    self.service.log_step_result(end_time=timestamp(),
                                                 message='%s %s - %s' % (
                                                     step.keyword, step.name, step.status.name.capitalize()),
                                                 level='TRACE',
                                                 item_id=scenario_info.scenario_id)
            #   Finishes scenario
            if scenario.status == 'failed':
                status = "FAILED"
            else:
                status = "PASSED"
            return self.service.finish_scenario_test(end_time=timestamp(),
                                                     status=status,
                                                     item_id=scenario_info.scenario_id)

    def after_feature(self, feature, feature_id):
        """
        Mark the feature as complete and set the status accordingly
        :param feature: the behave feature
        :param feature_id:  the id of the feature execution in order to mark it complete
        :return: response of the feature completion if available
        """
        if self.rp_enable:
            feature_info = Feature(feature, feature_id=feature_id)
            for scenario in feature.scenarios:
                try:
                    if 'skip' in scenario.tags:
                        scenario_id = self.service.start_scenario_test(name='Scenario: %s' % scenario.name,
                                                                       description=scenario.description,
                                                                       tags=feature_info.tags,
                                                                       start_time=timestamp(),
                                                                       item_type='SCENARIO',
                                                                       parent_item_id=feature_info.feature_id)
                        self.service.finish_scenario_test(end_time=timestamp(),
                                                          tatus='SKIPPED',
                                                          item_id=scenario_id)
                except IndexError:
                    pass
            #   Finishes feature
            if feature.status == 'failed':
                status = "FAILED"
            else:
                status = "PASSED"
            return self.service.finish_feature(end_time=timestamp(),
                                               status=status,
                                               item_id=feature_info.feature_id)

    def after_all(self, launch_id):
        """
        Finish the test execution and terminate the RP service
        :param launch_id: the id of the execution
        :return: response of the test execution completion if available
        """
        if self.rp_enable:
            launch_completion = self.service.finish_launcher(end_time=timestamp(), launch_id=launch_id)
            self.service.terminate_service()
            return launch_completion
