import sys
from logging import Logger

from cfgtools import Group
from dialogmanager import DialogManager
from model import TestConnectionData
from typing import Optional, Dict, Tuple, List

import paymentprocessor


class PaymentError(RuntimeError):
    def __repr__(self):
        return self.message


class PaymentNotMapped(PaymentError):
    def __init__(self, tender_id):
        self.message = "Payment {} not mapped".format(tender_id)

    def __repr__(self):
        return self.message


class ConfigurationNotFound(PaymentError):
    def __init__(self, configuration):
        self.message = "Configuration {} not found".format(configuration)


class PaymentNotFound(PaymentError):
    def __init__(self, payment_type):
        self.message = "Payment type {} not found".format(payment_type)


def _get_payment_class_reference(group_name):
    class_ref = getattr(sys.modules["paymentprocessor"], "{}PaymentProcessor".format(group_name), None)
    if class_ref is None:
        raise PaymentNotFound(group_name)
    return class_ref


class PaymentRouter(object):
    def __init__(self, message_bus, dialog_manager, config, logger):
        self.message_bus = message_bus
        self.dialog_manager = dialog_manager  # type: DialogManager
        self._config = config  # type: Group
        self._logger = logger  # type: Logger

        self._current_payment_processor = None  # type: paymentprocessor.PaymentProcessor
        self._payment_route = {}  # type: Dict[int, paymentprocessor.PaymentProcessor]
        self._payment_processors = []  # type: List[paymentprocessor.PaymentProcessor]
        self._fill_payment_routes()

    def _fill_payment_routes(self):
        try:
            electronic_types = self._config.find_group("ElectronicTypes")
            if not electronic_types:
                raise ConfigurationNotFound("ElectronicTypes")
            for group in electronic_types.groups:
                class_ref = _get_payment_class_reference(group.name)
                class_instance = class_ref(self.message_bus, self.dialog_manager, group, self._logger)
                self._payment_processors.append(class_instance)
                for tender_id in group.find_values("TenderIds") or []:
                    self._payment_route[int(tender_id)] = class_instance
                    self._logger.debug("Associated {} to tender id {}".format(group.name, tender_id))
        except Exception as _:
            self._logger.exception("Exception creating payment routes")
            raise

    def get_payment_processor(self, tender_id=None):
        # type: (Optional[int]) -> paymentprocessor.PaymentProcessor
        if tender_id is None:
            return self._current_payment_processor
        if tender_id not in self._payment_route:
            raise PaymentNotMapped(tender_id)
        self._current_payment_processor = self._payment_route[tender_id]
        return self._current_payment_processor

    def get_connection_status(self, test_connection_data):
        # type: (TestConnectionData) -> Tuple[Tuple[str, bool]]

        values = self._payment_route.itervalues()
        return tuple((p.get_name(), p.connection_status(test_connection_data)) for p in values)

    def stop_processors(self):
        for instance in self._payment_processors:
            instance.stop()

    def finish_all_payments(self, data):
        for processor in self._payment_processors:
            processor.finish_payment(data)
