from cmath import isnan
from distutils.log import debug
from enum import Enum
from select import select
import grpc
from json import dumps, loads
import os
from psutil import net_connections, process_iter
from psutil._common import CONN_LISTEN
from robomotion.utils import File
from robomotion import runner_pb2, runner_pb2_grpc


class AttachConfig:
    def __init__(self, protocol: str, addr: str, pid: int, namespace: str):
        self.protocol = protocol
        self.addr = addr
        self.pid = pid
        self.namespace = namespace


class EProtocol(Enum):
    ProtocolInvalid = ''
    ProtocolNetRPC = 'netrpc'
    ProtocolGRPC = 'grpc'


class Debug:
    atteched_to: str = ''

    @staticmethod
    def attach(g_addr: str, ns: str):
        cfg = AttachConfig(str(EProtocol.ProtocolGRPC.value),
                           g_addr, os.getpid(), ns)
        cfg_data = dumps(cfg.__dict__)

        Debug.atteched_to = Debug.get_rpc_addr()
        if Debug.atteched_to == '':
            print('empty gRPC address')
            exit(0)

        print('Attached to %s' % Debug.atteched_to)

        channel = grpc.insecure_channel(Debug.atteched_to)
        client = runner_pb2_grpc.DebugStub(channel)

        request = runner_pb2.AttachRequest(config=cfg_data.encode())
        client.Attach(request)
        channel.close()

    @staticmethod
    def detach(ns: str):
        if Debug.atteched_to == '':
            print('empty gRPC address')
            exit(0)

        channel = grpc.insecure_channel(Debug.atteched_to)
        client = runner_pb2_grpc.DebugStub(channel)

        request = runner_pb2.DetachRequest(namespace=ns)
        client.Detach(request)
        channel.close()

    @staticmethod
    def get_rpc_addr() -> str:
        tabs = Debug.get_netstat_ports(CONN_LISTEN, 'robomotion-runner')

        if len(tabs) == 0:
            return ""
        elif len(tabs) == 1:
            return '%s:%d' % (tabs[0].laddr.ip, tabs[0].laddr.port)

        return Debug.select_tab(tabs)

    @staticmethod
    def get_netstat_ports(state: str = CONN_LISTEN, process_name: str = ''):
        tabs = net_connections()

        pids = []
        for proc in process_iter():
            if process_name.lower() in proc.name().lower():
                pids.append(proc.pid)

        filtered = filter(lambda tab: tab.status ==
                          state and tab.pid in pids, tabs)
        return list(filtered)

    @staticmethod
    def select_tab(tabs: []) -> str:
        count = len(tabs)

        c = 0
        robots = ""
        for i in range(0, count):
            addr = '%s:%d' % (tabs[i].laddr.ip, tabs[i].laddr.port)
            name = Debug.get_robot_name(addr)
            if name == '':
                continue

            c += 1
            robots += '%d) %s\n' % (c, name)

        selected = 0
        print('Found %d robots running on the machine:' % c)
        print('%s' % robots)
        print('Please select a robot to attach (1-%d):' % c)

        while True:
            line = input()
            if not line.isnumeric():
                continue

            selected = int(line)
            if selected > 0 and selected <= count:
                tab = tabs[selected-1]
                return '%s:%d' % (tab.laddr.ip, tab.laddr.port)

    @staticmethod
    def get_robot_name(addr: str) -> str:
        try:
            channel = grpc.insecure_channel(addr)
            client = runner_pb2_grpc.RunnerStub(channel)

            request = runner_pb2.Null()
            resp = client.RobotName(request)
            channel.close()
            return resp.robot_name
        except:
            return ''
