from collections import Iterable
import os

from cacofonisk import BaseReporter
from cacofonisk import EventHandler
from cacofonisk.channel import SimpleChannel
from cacofonisk.runners.file_runner import FileRunner
from cacofonisk.utils.testcases import BaseTestCase


class TestReporter(BaseReporter):
    """
    TestReporter collects the events generated by EventHandler.

    The events can be retrieved later to see which events have been generated
    during the test.
    """

    def __init__(self):
        super(TestReporter, self).__init__()
        self.events = []

    def on_b_dial(self, caller, targets):
        self.events.append(('on_b_dial', {
            'caller': caller,
            'targets': sorted([target for target in targets]),
        }))

    def on_attended_transfer(self, caller, transferer, target):
        self.events.append(('on_attended_transfer', {
            'caller': caller,
            'transferer': transferer,
            'target': target,
        }))

    def on_blind_transfer(self, caller, transferer, targets):
        self.events.append(('on_blind_transfer', {
            'caller': caller,
            'transferer': transferer,
            'targets': sorted([target for target in targets])
        }))

    def on_blonde_transfer(self, caller, transferer, targets):
        self.events.append(('on_blonde_transfer', {
            'caller': caller,
            'transferer': transferer,
            'targets': sorted([target for target in targets])
        }))

    def on_up(self, caller, target):
        self.events.append(('on_up', {
            'caller': caller,
            'target': target,
        }))

    def on_hangup(self, caller, reason):
        self.events.append(('on_hangup', {
            'caller': caller,
            'reason': reason,
        }))

    def on_queue_caller_abandon(self, caller):
        self.events.append(('on_queue_caller_abandon', {
            'caller': caller,
        }))

class BogoRunner(object):
    def __init__(self, events, reporter):
        self.events = events
        self.reporter = reporter
        self.channel_managers = []

    def run(self):
        handler = EventHandler(reporter=self.reporter)
        interesting_events = handler.event_handlers().keys()
        for event in self.events:
            if (
                    not handler.FILTER_EVENTS or
                    event['Event'] in interesting_events
            ):
                handler.on_event(event)

        self.channel_managers.append(handler)


class ChannelEventsTestCase(BaseTestCase):
    """
    Run event tests based on the JSON sample data.
    """
    def events_from_tuples(cls, tuples):
        """
        Convert a list of tuples to the expected event list.

        Args:
            tuples: An iterable with iterables, see example.

        Returns:
            tuple: A tuple of dictionaries, see example.
        """
        results = []

        for data in tuples:
            event_name, event_data = data
            event_data['event'] = event_name
            results.append(event_data)

        return tuple(results)

    def run_and_get_events(self, filename, reporter=None):
        absolute_path = os.path.join(os.path.dirname(__file__), filename)

        if reporter is None:
            reporter = TestReporter()

        runner = FileRunner([absolute_path], reporter=reporter)
        runner.run()

        assert len(runner.channel_managers) == 1
        return reporter.events

    def _pluck_channel_name_from_event(self, event):
        name, data = event
        data = data.copy()

        for key, value in data.items():
            if isinstance(value, SimpleChannel):
                data[key] = value.name
            elif isinstance(value, Iterable) and not isinstance(value, str):
                data[key] = [item.name for item in value]

        return name, data

    def assertEqualChannels(self, expected, actual):
        actual_channels = [self._pluck_channel_name_from_event(event) for
                           event in actual]

        self.assertEqual(expected, actual_channels)
