""" Pytest behavior customizations.
"""

import os
import sys
import time
import re
import itertools
import logging

import pytest
from _pytest.terminal import TerminalReporter

from config import load_config, default
from reporting import ReportSingleton, TestFunction, TestReport


class AgentTerminalReporter(TerminalReporter):
    """ Customized PyTest Output """
    @pytest.hookimpl(trylast=True)
    def pytest_sessionstart(self, session):
        self._session = session
        self._sessionstarttime = time.time()

    def pytest_runtest_logstart(self, nodeid, location):
        line = self._locationline(nodeid, *location)
        self.write_sep('=', line, bold=True)
        self.write('\n')


def pytest_addoption(parser):
    """ Load in config path. """
    group = parser.getgroup(
        "Aries Protocol Test Suite Configuration",
        "Aries Protocol Test Suite Configuration",
        after="general"
    )
    group.addoption(
        "--sc",
        "--suite-config",
        dest='suite_config',
        action="store",
        metavar="SUITE_CONFIG",
        help="Load suite configuration from SUITE_CONFIG",
    )
    group.addoption(
        "-S",
        "--select",
        dest='select',
        action='store',
        metavar='SELECT_REGEX',
        help='Run tests matching SELECT_REGEX. '
        'Overrides tests selected in configuration.'
    )
    group.addoption(
        "-O",
        "--output",
        dest="save_path",
        action="store",
        metavar="PATH",
        help="Save interop profile to PATH."
    )
    group.addoption(
        "-L",
        "--list",
        dest="list_tests",
        action="store_true",
        help="List available tests."
    )
    group.addoption(
        "--show-dev-notes",
        dest="dev_notes",
        action="store_true",
        help="Output log messages generated during testing for developers\n"
             "take note of."
    )


@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    """ Load Test Suite Configuration. """
    dirname = os.getcwd()
    config_path = config.getoption('suite_config')
    config_path = 'config.toml' if not config_path else config_path
    config_path = os.path.join(dirname, config_path)
    print(
        '\nAttempting to load configuration from file: %s\n' %
        config_path
    )

    try:
        config.suite_config = load_config(config_path)
    except FileNotFoundError:
        config.suite_config = default()
    config.suite_config['save_path'] = config.getoption('save_path')

    # Override default terminal reporter for better test output when not capturing
    if config.getoption('capture') == 'no':
        reporter = config.pluginmanager.get_plugin('terminalreporter')
        agent_reporter = AgentTerminalReporter(config, sys.stdout)
        config.pluginmanager.unregister(reporter)
        config.pluginmanager.register(agent_reporter, 'terminalreporter')

    # Compile select regex and test regex if given
    select_regex = config.getoption('select')
    config.select_regex = re.compile(select_regex) if select_regex else None
    config.tests_regex = list(map(
        re.compile, config.suite_config['tests']
    ))


def pytest_collection_modifyitems(session, config, items):
    """Select tests based on config or args."""
    # pylint: disable=protected-access
    if not items:
        return

    report = ReportSingleton(session.config.suite_config)

    def add_to_report(item):
        if callable(item._obj) and hasattr(item._obj, 'meta_set'):
            func = item._obj
            test_fn = TestFunction(
                protocol=func.protocol,
                version=func.version,
                role=func.role,
                name=func.name,
                description=func.__doc__
            )
            item.meta_name = test_fn.flatten()['name']
            report.add_test(test_fn)
        return item

    def test_regex_filter(item):
        for regex in config.tests_regex:
            if regex.match(item.meta_name):
                return True
        return False

    item_pipeline = map(add_to_report, items)
    if config.select_regex:
        item_pipeline = filter(
            lambda item: bool(config.select_regex.match(item.meta_name)),
            item_pipeline
        )
    elif config.tests_regex:
        item_pipeline = filter(
            test_regex_filter,
            item_pipeline
        )

    remaining = list(item_pipeline)
    deselected = list(set(items)-set(remaining))

    # Report the deselected items to pytest
    config.hook.pytest_deselected(items=deselected)

    items[:] = remaining


@pytest.hookimpl()
def pytest_report_collectionfinish(config, startdir, items):
    """Print available tests if option set."""
    if config.getoption('list_tests'):
        reporter = config.pluginmanager.get_plugin('terminalreporter')
        reporter.write('\n')
        reporter.write_sep('-', 'Available Tests', bold=False, yellow=True)
        return ReportSingleton(config.suite_config).available_tests_json()


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """ Customize reporting """
    outcome = yield
    report = outcome.get_result()

    setattr(item, "report_" + report.when, report)


def pytest_terminal_summary(terminalreporter, exitstatus, config):
    """Write Interop Profile to terminal summary."""
    if config.getoption('collectonly'):
        return

    report = ReportSingleton(config.suite_config)

    if config.getoption('dev_notes'):
        terminalreporter.write('\n')
        terminalreporter.write_sep(
            '=', 'Developer Notes', bold=True
        )
        terminalreporter.write('\n')
        terminalreporter.write(report.notes_json())
        terminalreporter.write('\n')



    terminalreporter.write('\n')
    terminalreporter.write_sep(
        '=', 'Interop Profile', bold=True
    )
    terminalreporter.write('\n')
    terminalreporter.write(report.report_json())
    terminalreporter.write('\n')


@pytest.fixture(scope='session')
def report(config):
    """Report fixture."""
    report_instance = ReportSingleton(config)
    yield report_instance
    save_path = config.get('save_path')
    if save_path:
        report_instance.save(save_path)


@pytest.fixture
def report_on_test(request, caplog, report):
    """Universally loaded fixture for getting test reports."""
    yield
    passed = False
    if hasattr(request.node, 'report_call') and \
            request.node.report_call.outcome == 'passed':
        passed = True


    test_fn = TestFunction(
        protocol=request.function.protocol,
        version=request.function.version,
        role=request.function.role,
        name=request.function.name,
        description=request.function.__doc__
    )

    report.add_report(TestReport(test_fn, passed))

    notes = itertools.chain([
        records for when in ('setup', 'call', 'teardown')
        for records in caplog.get_records(when)
    ])
    notes = filter(
        lambda log_rec: log_rec.levelno >= logging.WARNING,
        notes
    )
    notes = map(
        lambda log_rec: log_rec.message,
        notes
    )
    report.add_notes(
        test_fn,
        notes
    )