import time
import threading
import socket
from queue import Queue
from pymasep.communication import SocketHandler, Server, Client, Frame

"""This is the only tests where a real TCP socket is used"""


# Thanks https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib
def _get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.settimeout(0)
    try:
        # doesn't even have to be reachable
        s.connect(('10.254.254.254', 1))
        ip = s.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        s.close()
    return ip


class ReceiveHandlerPing(SocketHandler):

    def on_receive(self, frame):
        self.send_frame_async(frame)


class ReceiveHandlerClient(SocketHandler):

    def __init__(self, conn, addr, logger, q):
        super().__init__(conn, addr, logger)
        self.queue = q

    def on_receive(self, frame):
        self.queue.put(frame)
        self.send_frame_async(Frame.bye_frame())


def test_ping():
    expected_message = Frame(b'One Message')
    port = 50000
    q = Queue()

    def server_func():
        server = Server(ident='server',
                        host='localhost',
                        port=port,
                        max_connection=1,
                        socket_handler_class=ReceiveHandlerPing,
                        socket_handler_args=tuple())
        server.wait_connection()
        # wait for accepting client connection
        time.sleep(0.5)
        server.stop_wait_connection()
        server.wait_connection_thread.join()
        assert isinstance(server.get_connection('client'), ReceiveHandlerPing)

    def client_func():
        client = Client(ident='client',
                        host='localhost',
                        port=port,
                        socket_handler_class=ReceiveHandlerClient,
                        socket_handler_args=(q,))
        client.connect()
        client.send_frame_to('server', expected_message)

        assert isinstance(client.get_connection('server'), ReceiveHandlerClient)
        # wait for all messages
        client.get_connection(client.server_id).join()

    # maybe should use this :
    # https://stackoverflow.com/questions/45701904/python-unittest-and-multiprocessing-fail-when-run-sequentially
    p_serv = threading.Thread(name='Server', target=server_func)
    p_client = threading.Thread(name='Client', target=client_func)
    p_serv.start()
    p_client.start()
    p_serv.join()
    p_client.join()

    result = q.get()
    assert result == expected_message


def test_announcement():
    port = 50004
    q = Queue()

    def server_func():
        server = Server(ident='server',
                        host='localhost',
                        port=port,
                        max_connection=1,
                        socket_handler_class=SocketHandler,
                        socket_handler_args=tuple(),
                        wait_announcement=True)
        server.wait_announcement()
        # wait for announcement
        time.sleep(0.5)
        server.stop_wait_announcement()
        server.wait_announcement_thread.join()

    def client_func():
        client = Client(ident='client',
                        socket_handler_class=ReceiveHandlerClient,
                        socket_handler_args=(q,))
        client.search_server()
        res = client.host + ':' + str(client.port)
        q.put(res)
        res_type = type(client.host) == str and type(client.port) == int
        q.put(str(res_type))

    p_serv = threading.Thread(name='Server', target=server_func)
    p_client = threading.Thread(name='Client', target=client_func)
    p_serv.start()
    time.sleep(0.1)
    p_client.start()
    p_serv.join()
    time.sleep(0.1)
    p_client.join()

    local_ip = _get_ip()
    result = q.get()
    result_type = q.get()
    assert result == local_ip + ':' + str(port)
    assert result_type == str(True)
