"""This module contains changed pytest for report-portal."""

# This program is free software: you can redistribute it
# and/or modify it under the terms of the GPL licence

import logging
from os import getenv
import dill as pickle
import pkg_resources
import pytest
import requests
import time
from pytest_reportportal import LAUNCH_WAIT_TIMEOUT
from .service import PyTestServiceClass
from .listener import RPReportListener

try:
    # This try/except can go away once we support pytest >= 3.3
    pkg_resources.get_distribution('pytest >= 3.3.0')
    PYTEST_HAS_LOGGING_PLUGIN = True
    try:
        # This try/except can go away once we support pytest >= 5.4.0
        from _pytest.logging import get_actual_log_level
    except ImportError:
        from _pytest.logging import get_log_level_for_setting as \
            get_actual_log_level
except pkg_resources.VersionConflict:
    PYTEST_HAS_LOGGING_PLUGIN = False

log = logging.getLogger(__name__)


def is_master(config):
    """
    Validate slaveinput attribute.

    True if the code running the given pytest.config object
    is running in a xdist master node or not running xdist at all.
    """
    return not hasattr(config, 'slaveinput')


@pytest.mark.optionalhook
def pytest_configure_node(node):
    """
    Configure node of tests.

    :param node: _pytest.nodes.Node
    :return: pickle of RPService
    """
    if node.config._reportportal_configured is False:
        # Stop now if the plugin is not properly configured
        return
    node.slaveinput['py_test_service'] = pickle.dumps(node.config.
                                                      py_test_service)


def pytest_sessionstart(session):
    """
    Start test session.

    :param session: Session
    :return: None
    """
    if session.config._reportportal_configured is False:
        # Stop now if the plugin is not properly configured
        return

    if is_master(session.config):
        session.config.py_test_service.init_service(
            project=session.config.getini('rp_project'),
            endpoint=session.config.getini('rp_endpoint'),
            uuid=getenv('RP_UUID') or session.config.getini('rp_uuid'),
            log_batch_size=int(session.config.getini('rp_log_batch_size')),
            ignore_errors=bool(session.config.getini('rp_ignore_errors')),
            ignored_attributes=session.config.getini('rp_ignore_attributes'),
            verify_ssl=session.config.getini('rp_verify_ssl'),
            retries=int(session.config.getini('retries')),
        )

        attributes = [{'value': tag} for tag in
                      session.config.getini('rp_launch_attributes')]
        session.config.py_test_service.start_launch(
            session.config.option.rp_launch,
            attributes=attributes,
            description=session.config.option.rp_launch_description
        )
        if session.config.pluginmanager.hasplugin('xdist'):
            wait_launch(session.config.py_test_service.rp)


def pytest_collection_finish(session):
    """
    Collect tests if session is configured.

    :param session: pytest.Session
    :return: None
    """
    if session.config._reportportal_configured is False:
        # Stop now if the plugin is not properly configured
        return

    session.config.py_test_service.collect_tests(session)


def wait_launch(rp_client):
    """
    Wait for initialize RP_Service.

    :param rp_client: RP_Service
    :return: None
    """
    timeout = time.time() + LAUNCH_WAIT_TIMEOUT
    while not rp_client.launch_id:
        if time.time() > timeout:
            raise Exception("Launch not found")
        time.sleep(1)


def pytest_sessionfinish(session):
    """
    Finish session if has attr  'slaveinput'.

    :param session: pytest.Session
    :return: None
    """
    if session.config._reportportal_configured is False:
        # Stop now if the plugin is not properly configured
        return

    if is_master(session.config):
        session.config.py_test_service.finish_launch()


def pytest_configure(config):
    """
    Configure RPReportListener for send logs.

    :param config: Config file
    :return:  None
    """
    if config.getoption('--collect-only', default=False) or \
            config.getoption('--setup-plan', default=False) or \
            not config.option.rp_enabled:
        config._reportportal_configured = False
        return

    project = config.getini('rp_project')
    endpoint = config.getini('rp_endpoint')
    uuid = getenv('RP_UUID') or config.getini('rp_uuid')
    ignore_errors = config.getini('rp_ignore_errors')
    config._reportportal_configured = all([project, endpoint, uuid])

    if config._reportportal_configured and ignore_errors:
        try:
            verify_ssl = config.getini('rp_verify_ssl')
            r = requests.get(
                '{0}/api/v1/project/{1}'.format(endpoint, project),
                headers={
                    'Authorization': 'bearer {0}'.format(uuid)
                },
                verify=verify_ssl
            )
            r.raise_for_status()
        except requests.exceptions.RequestException as exc:
            log.exception(exc)
            config._reportportal_configured = False

    if config._reportportal_configured is False:
        return

    if not config.option.rp_launch:
        config.option.rp_launch = config.getini('rp_launch')
    if not config.option.rp_launch_description:
        config.option.rp_launch_description = config.\
            getini('rp_launch_description')

    if is_master(config):
        config.py_test_service = PyTestServiceClass()
    else:
        config.py_test_service = pickle.loads(config.
                                              slaveinput['py_test_service'])

    # set Pytest_Reporter and configure it
    if PYTEST_HAS_LOGGING_PLUGIN:
        # This check can go away once we support pytest >= 3.3
        log_level = get_actual_log_level(config, 'rp_log_level')
        if log_level is None:
            log_level = logging.NOTSET
    else:
        log_level = logging.NOTSET

    config._reporter = RPReportListener(config.py_test_service,
                                        log_level=log_level,
                                        endpoint=endpoint)

    if hasattr(config, '_reporter'):
        config.pluginmanager.register(config._reporter)


def pytest_unconfigure(config):
    """
    Clear config from reporter.

    :param config: Config file
    :return: None
    """
    if config._reportportal_configured is False:
        # Stop now if the plugin is not properly configured
        return

    if hasattr(config, '_reporter'):
        reporter = config._reporter
        del config._reporter
        config.pluginmanager.unregister(reporter)
        log.debug('RP is unconfigured')


def pytest_addoption(parser):
    """
    Add parameter in config of reporter.

    :param parser: Config
    :return:  None
    """
    group = parser.getgroup('reporting')
    group.addoption(
        '--rp-launch',
        action='store',
        dest='rp_launch',
        help='Launch name (overrides rp_launch config option)')
    group.addoption(
        '--rp-launch-description',
        action='store',
        dest='rp_launch_description',
        help='Launch description (overrides '
             'rp_launch_description config option)')

    group.addoption(
        '--reportportal',
        action='store_true',
        dest='rp_enabled',
        default=False,
        help='Enable ReportPortal plugin'
    )

    if PYTEST_HAS_LOGGING_PLUGIN:
        group.addoption(
            '--rp-log-level',
            dest='rp_log_level',
            default=None,
            help='Logging level for automated log records reporting'
        )
        parser.addini(
            'rp_log_level',
            default=None,
            help='Logging level for automated log records reporting'
        )

    parser.addini(
        'rp_uuid',
        help='UUID')

    parser.addini(
        'rp_endpoint',
        help='Server endpoint')

    parser.addini(
        'rp_project',
        help='Project name')

    parser.addini(
        'rp_launch',
        default='Pytest Launch',
        help='Launch name')

    parser.addini(
        'rp_launch_attributes',
        type='args',
        help='Launch attributes, i.e Performance Regression')

    parser.addini(
        'rp_tests_attributes',
        type='args',
        help='Attributes for all tests items, e.g. Smoke')

    parser.addini(
        'rp_launch_description',
        default='',
        help='Launch description')

    parser.addini(
        'rp_log_batch_size',
        default='20',
        help='Size of batch log requests in async mode')

    parser.addini(
        'rp_ignore_errors',
        default=False,
        type='bool',
        help='Ignore Report Portal errors (exit otherwise)')

    parser.addini(
        'rp_ignore_attributes',
        type='args',
        help='Ignore specified pytest markers, i.e parametrize')

    parser.addini(
        'rp_hierarchy_dirs_level',
        default=0,
        help='Directory starting hierarchy level')

    parser.addini(
        'rp_hierarchy_dirs',
        default=False,
        type='bool',
        help='Enables hierarchy for directories')

    parser.addini(
        'rp_hierarchy_module',
        default=True,
        type='bool',
        help='Enables hierarchy for module')

    parser.addini(
        'rp_hierarchy_class',
        default=True,
        type='bool',
        help='Enables hierarchy for class')

    parser.addini(
        'rp_hierarchy_parametrize',
        default=False,
        type='bool',
        help='Enables hierarchy for parametrized tests')

    parser.addini(
        'rp_issue_marks',
        type='args',
        default='',
        help='Pytest marks to get issue information')

    parser.addini(
        'rp_issue_system_url',
        default='',
        help='URL to get issue description. Issue id '
             'from pytest mark will be added to this URL')

    parser.addini(
        'rp_verify_ssl',
        default=True,
        type='bool',
        help='Verify HTTPS calls')

    parser.addini(
        'rp_display_suite_test_file',
        default=True,
        type='bool',
        help="In case of True, include the suite's relative"
             " file path in the launch name as a convention of "
             "'<RELATIVE_FILE_PATH>::<SUITE_NAME>'. "
             "In case of False, set the launch name to be the suite name "
             "only - this flag is relevant only when"
             " 'rp_hierarchy_module' flag is set to False")

    parser.addini(
        'rp_issue_id_marks',
        type='bool',
        default=True,
        help='Adding tag with issue id to the test')

    parser.addini(
        'retries',
        default='0',
        help='Amount of retries for performing REST calls to RP server')