from gevent import monkey;monkey.patch_all()
import gevent
from gevent import signal
from gevent.server import StreamServer

from datetime import datetime
from typing import List
import logging
from bee.net.rpc.registry.builder import Builder
from bee.net.rpc.registry.registry import Server as RegistryServer

from bee.net.rpc.channel import Channel
from bee.net.rpc.codec import RequestHead, IServerCodec, Response, ResponseHead, Result, \
    Stream, get_codec
from bee.net.rpc.registry.direct_registry import DirectRegistry
from bee.net.rpc.transport import Address

from bee.data.guid.guid import Guid
from bee.data.map import Map
from bee.errors.error import BeeError
from bee.net.rpc.registry.registry import Registry
from bee.data import const

from bee import config

logging.basicConfig(level=logging.DEBUG)
is_debug = True


class ServerLoadError(BeeError):
    pass


class ReadPeeker(object):
    pass


class MatchInfo():
    """
    codec macher
    """

    def __init__(self, codec: str = "proto", opts: Map = None):
        self.codec = codec
        self.opts = opts

    def match(self, p: ReadPeeker) -> bool:
        """
        偷瞄两眼，看看是什么编码，吼吼吼
        :param p:
        :return:
        """
        pass


class Session():

    def id(self) -> str:
        pass

    def local_addr(self) -> str:
        pass

    def remote_addr(self) -> str:
        pass


class session(Session):

    def __init__(self, ch: Stream, sc: IServerCodec, heartbeat: datetime = datetime.now()):
        self.channel = ch
        self.sc = sc
        self.heartbeat = heartbeat

    def id(self) -> str:
        return self.channel.id()


def new_session(ch: Stream, sc: IServerCodec) -> session:
    return session(ch=ch, sc=sc, heartbeat=datetime.now())


class SessionMap():

    def __init__(self):
        self.channels = Map()

    def add(self, s: session):
        self.channels.set(s.id(), s)

    def get(self, id: str):
        self.channels.get(id)

    def remove(self, id: str):
        self.channels.remove(id)

    def count(self):
        return len(self.channels)


class ServerOptions(Map):

    def __init__(self, name: str = None, desc: str = None, version: str = "1.0", macher: str = "proto",
                 address: Address = None
                 , max_conn_size: int = 1024, max_pool_size: int = 1024, backlog: int = 1000
                 , read_timeout: int = 10, write_timeout: int = 10):
        self.id = Guid().string()
        self.name = name
        self.desc = desc
        self.version = version
        self.macher = macher
        self.address = address
        self.max_conn_size = max_conn_size
        self.max_pool_size = max_pool_size
        self.backlog = backlog
        self.read_timeout = read_timeout
        self.write_timeout = write_timeout


class Server():

    def __init__(self, opts: ServerOptions, registry: Registry = None):
        self.opts = opts

        if registry == None:
            self.registry = DirectRegistry(url=opts.address.url)
        else:
            self.registry = registry

        # 初始化service管理容器
        self.services = Map()

        # 初始化session管理容器
        self.sessions = SessionMap()

        # 初始化编解码器
        self.init_codec_builder()


    @staticmethod
    def new_server(name: str = None, macher: str = "proto", address: Address = Address(url="127.0.0.1:9000"),
                   registry: Registry = None) -> "Server":
        opts = ServerOptions(name=name, macher=macher, address=address,)
        if is_debug:
            logging.debug("bee.rpc > init server's options")
        s = Server(opts=opts, registry=registry)
        if is_debug:
            logging.debug("bee.rpc > new server...")
        return s

    def register_service(self, ins):
        """register rpc service"""
        if not ins:
            return
        name = ins.__class__.__name__
        self.services.set(name, ins)

    def handle(self, sock, address):
        print("address= " + str(address))
        while True:
            if sock.closed == True:
                print("closed...")
                break
            self.handle_request(sock, address)
        sock.close()

    def init_codec_builder(self):
        if self.opts.macher == "http":
            pass
        elif self.opts.macher == "json":
            b = get_codec("json")
            if not b:
                import bee.net.rpc.codecs.json.json as json
                json.init()
        else:  # proto
            b = get_codec("proto")
            if not b:
                import bee.net.rpc.codecs.proto.proto as proto
                proto.init()

    def new_server_codec(self, sock) -> IServerCodec:
        m = self.opts.macher
        if m == None:
            raise ServerLoadError("no macher!")
        else:
            b = get_codec(m)
            if not b:
                raise ServerLoadError("init codec builder error!")
            return b.new_server_codec(s=Channel(id=Guid().string(), sock=sock), opts=Map())

    def add_session(self, sc) -> session:
        s = new_session(sc.stream(), sc)
        self.sessions.add(s)
        return s

    def remove_session(self, sc) -> session:
        self.sessions.remove(sc.stream().id())

    def handle_request(self, sock, address):
        rh = RequestHead()
        args = []
        sc = self.new_server_codec(sock)
        sc.decode_head(rh)
        if rh.id == None or rh.id == 0:
            sock.close()
        else:
            # print("rh.id=" + str(rh.id))
            sc.decode_args(args)
            self.add_session(sc)
            self.invoke_service(sc, rh=rh, args=args)
            self.remove_session(sc)

    def invoke_service(self, sc, rh: RequestHead, args: List):
        id = rh.id
        sname = rh.service
        s = self.services.get(sname)
        mname = rh.method
        if s != None:
            ret = getattr(s, mname)(*(args))
            head = ResponseHead(id=id, assets=None)
            result = Result(value=ret, error=None)
            resp = Response(head=head, result=result)
            sc.encode(resp)
        else:
            print("do nothing")

    def find_codec_builder(self, macher: str) -> IServerCodec:
        cb = const.codecs.get(macher)
        return cb

    def register(self):
        self.nid = Guid().string()
        self.registry.register(self.opts.name, self.nid, self.opts.address.url)

    def handle_signal(self):
        """register signal"""

        def handler(signum, frame):
            logging.info("bee.rpc > kill service={}, nid={}".format(self.opts.name, self.nid))
            self.registry.deregister(self.opts.name, self.nid)

        signal.signal(signal.SIGINT, handler)
        signal.signal(signal.SIGTERM, handler)

    def startup(self):
        """
        start the rpc server
        """
        # if not self.handler:
        #     raise ServerLoadError('Methods not exits.')

        self.handle_signal()

        pair = self.opts.address.url.split(":")
        server = StreamServer(
            (pair[0], int(pair[1])),
            self.handle,
            backlog=self.opts.backlog,
            spawn=self.opts.max_conn_size)

        self.register()

        server.serve_forever()
        self.srv = server

    def shutdown(self):
        """
        shutdown the rpc server
        """
        if self.registry != None:
            self.registry.close()

        if self.srv != None:
            self.srv.close()

    def __call__(environ, start_response):
        # for gunicorn wsgi app
        pass

def auto_server():

    if config.exist("debug"):
        is_debug = config.get("debug")
    bee_rpc_registry_conf = config.get("bee.rpc.registry")
    registry_server = RegistryServer()
    registry_server.__dict__ = bee_rpc_registry_conf
    registry = Builder.build(registry_server)

    bee_rpc_server_conf = config.get("bee.rpc.server")
    if bee_rpc_server_conf != None:
        for k, v in dict(bee_rpc_server_conf).items():
            opts = ServerOptions()
            opts.name = k
            opts.cover(Map.from_dict(v))
            srv = Server(opts=opts, registry=registry)
            srv.startup()

            logging.info("bee.rpc > server start success")

