from aionetiface import *
from aionetiface.testing import AsyncTestCase
from port_helpers import xdist_port_base


class TestDaemon(AsyncTestCase):
    async def test_daemon(self):
        loop = asyncio.get_event_loop()
        print(loop)

        protos = (TCP, UDP)
        server_port = xdist_port_base(34200)
        loopbacks = {IP4: "127.0.0.1", IP6: "::1"}

        at_least_one = False
        i = 0
        interface = await Interface()

        """
        for s in aionetiface_fds:
            print(s._closed)

        print(aionetiface_fds)
        return
        """

        afs = [IP4]
        for af in afs:
            log(fstr("Test daemon af = {0}", (af,)))

            """
            (1) Get first route for AF type.
            (2) Use in-built method and manually specify bind IP of '*'.
            (3) For IP4 this will bind to 0.0.0.0.
            (4) For IP6 this will bind to ::.
            (5) Will test IP4 NIC IPs, IP6 link locals / local host.
            """
            try:
                route = await interface.route(af)
            except (ValueError, AttributeError):
                continue
            addrs = [route.nic(), loopbacks[af], "*"]

            # Test connect to link local.
            if af == IP6:
                addrs.append(route.link_local())

            addrs = [route.nic()]
            for addr in addrs:
                log(fstr("test daemon addr = {0}", (addr,)))
                msg = b"hello world ay lmaoo"
                for proto in protos:
                    log(fstr("test daemon proto = {0}", (proto,)))
                    # print()
                    # print(proto)
                    # print(addr)

                    # Fresh route per server.
                    i += 1
                    echo_route = await interface.route(af).bind(
                        ips=addr, port=server_port + i
                    )
                    # print(echo_route)
                    # print(echo_route._bind_tups)

                    # Daemon instance.
                    echod = EchoServer()
                    await echod.add_listener(proto, echo_route)

                    if addr == "*":
                        addr = "localhost"

                    dest = (addr, server_port + i)

                    # Spawn a pipe to the echo server.
                    test_route = await interface.route(af).bind(ips=addr)
                    pipe = await Pipe(proto, dest, test_route).connect()
                    try:
                        self.assertTrue(pipe is not None)

                        # Indicate to save all messages to a queue.
                        pipe.subscribe(SUB_ALL)

                        # Send message to server.
                        # print(dest.tup)
                        send_ret = await pipe.send(msg, dest)

                        # Receive data back.
                        data = await asyncio.wait_for(pipe.recv(SUB_ALL), timeout=10)
                        self.assertEqual(data, msg)

                        # Test accept() await.
                        # Send message from pipe to server's client pipe.
                        # Then manually call it's receive and check for receipt.
                        client_pipe = await pipe.pipe_events
                        self.assertTrue(client_pipe is not None)
                        client_pipe.subscribe(SUB_ALL)
                        await pipe.send(msg, dest)
                        data = await asyncio.wait_for(client_pipe.recv(SUB_ALL), timeout=10)
                        self.assertEqual(data, msg)
                        print(proto, dest, "works")
                    finally:
                        """
                        Making sure cleanup works correctly is very important
                        because if they restart a server program it will
                        most probably try listen to the same address and that
                        will throw an 'address already in use' error if the
                        socket wasn't cleaned up correctly. The code here
                        will fail if cleanup for these servers isn't correct.

                        Does end up clearing servers FDs.
                        """

                        if echod is not None:
                            await echod.close()

                        if pipe is not None:
                            await pipe.close()

        # await asyncio.sleep(4)
        print(aionetiface_fds)
        await asyncio.sleep(0.1)

        """
        one socket can potentially be unclosed simulating their client computer
        keeping it open if it wasn't shutdown cleanly esp for UDP this
        is expected since its designed to be connectionless.
        breaking a con at one end can close the other with TCP but unless
        a manual proto is done for udp the socket still exists
        """


if __name__ == "__main__":
    main()

"""
echo server tcp stream socket not being closed.
"""
