import glob
import logging

from ..common import *


class PluginInitializer:
    """Facility to set some static variables for plugin classes"""

    def __init__(self, plugin_dir, log, cls):
        self.plugin_dir, self.log, self.cls = plugin_dir, log, cls

    def init_plugin(self):
        """Load plugin configurations and set up class to using in app"""
        name = self.cls.NAME
        mark = self.cls.MARK.decode('ascii')
        main_conf_path = os.path.join(self.plugin_dir, '{}.conf.json'.format(mark))
        res = ConfigLoader(main_conf_path, self.cls.CONFIG_SCHEMA, True).load()
        if isinstance(res, str):
            self.log.error(f'Plugin "{name}" init fail: {res}')
            return False
        self.cls.MAIN_CONF = res
        for path in glob.glob(os.path.join(self.plugin_dir, '*.{}.conf.json'.format(mark))):
            res = ConfigLoader(path, self.cls.CONFIG_SCHEMA, False).load()
            if isinstance(res, str):
                self.log.warning(f'Plugin "{name}" dev conf fail: {res}')
            else:
                if res.get('device') is not None:
                    self.cls.DEVICE_CONFS[res['device']] = res
                else:
                    self.log.warning(f'Plugin "{name}" dev conf fail: no UIN set in device config')
        try:
            return self.cls.post_init()
        except Exception as e:
            self.log.error('Initialization error for plugin {}'.format(name))
            self.log.exception(e)
        return False


class Plugin:
    """Base plugin class"""
    MARK = b'\0\0\0\0'
    NAME = ''
    CONF_SCHEMA = None
    DEFAULT_CONF = dict(device=None)
    MAIN_CONF = dict()
    DEVICE_CONFS = dict()

    def __init__(self, app, handler, device):
        self.app, self.logger, self.handler, self.sock, self.device = app, app.log, handler, handler.sock, device

    def log(self, message, level=logging.INFO):
        """Make log record with specified level"""
        self.logger.log(level, '[{}] {}'.format(self.NAME, message))

    @classmethod
    def post_init(cls):
        """Plugin-specific initialization, this function is called by plugin manager in the end of plugin init"""
        return True

    def conf(self, path):
        """Get value from config using path - key or key sequence.
        If uin is integer - try get from device-specific conf"""
        uin = self.device.uin
        conf = self.DEVICE_CONFS.get(uin, self.MAIN_CONF)
        if not isinstance(path, (tuple, list)):
            path = (path, )
        node = conf
        for i in path:
            try:
                node = node[i]
            except (KeyError, IndexError):
                return
            except TypeError:
                self.log('TypeError in plugin {} while config value {}'.format(self.NAME, path), logging.ERROR)
                return
        return node

    def read(self) -> Optional[bytes]:
        """Read message from socket"""
        size_raw = self.handler.recv(4)
        if size_raw is None:
            return None
        encrypted = self.handler.recv(int.from_bytes(size_raw, 'big'))
        if encrypted is None:
            return
        # key = SHA256.new(self.device.key_recv + self.handler.salt_recv)
        key = self.device.key_recv
        return decrypt(encrypted, key)

    def send(self, buf: bytes):
        """Send message to socket"""
        # key = SHA256.new(self.device.key_send + self.handler.salt_send)
        self.sock.sendall((len(buf) + 32).to_bytes(4, 'big') + encrypt(buf, self.device.key_send))

    def rpc_read(self) -> Optional[RPCRequest]:
        """Read JSON-RPC 2.0 requests/notifications"""
        request_raw = self.read()
        if request_raw is None:
            return
        self.log(request_raw, logging.DEBUG)
        try:
            return RPCRequest.from_dict(json.loads(request_raw.decode()))
        except BaseException as e:
            self.log(e, logging.WARNING)

    def rpc_send(self, obj):
        """Send JSON-RPC 2.0 response, notification or request"""
        try:
            serialized_obj = json.dumps(obj.to_dict())
            self.send(serialized_obj.encode())
            self.log("Sent: {}".format(serialized_obj), logging.DEBUG)
        except BaseException as e:
            self.log(e, logging.WARNING)

    def main(self):
        """Entry point for plugins called in TCP handler"""
        raise NotImplementedError
