import asyncio
import socket
import etcd3
from aiohttp import web

def find_free_port():
    s = socket.socket()
    s.bind(("", 0))
    port = s.getsockname()[1]
    s.close()
    return port

class ClosingHandler:
  def __init__(self, exit_event):
    self.exit_event = exit_event

  def next_port():
    return self.next_port

  async def default(self, request):
      self.exit_event.set()
      self.next_port = find_free_port()
      return web.HTTPOk(
        body=f"HELLO, GOODBYE {self.next_port}\n"
      )

#TODO traefik class
def setup_traefik_srs(etcd):
    etcd.delete_prefix("traefik/http/routers/srs/")
    etcd.delete_prefix("traefik/http/services/srs/")
    etcd.put("traefik/http/middlewares/retry502/buffering/retryExpression", "ResponseCode() == 502")
    etcd.put("traefik/http/routers/srs/rule","Host(`srs.trustme.click`)")
    etcd.put("traefik/http/routers/srs/service","srs")
    etcd.put("traefik/http/routers/srs/entrypoints/0","websecure")
    etcd.put("traefik/http/routers/srs/tls/certResolver","tmc-acme-http")
    etcd.put("traefik/http/routers/srs/middlewares/0","retry502")
    print("etcd srs service setup complete")

def add_traefik_srs_url(etcd, address, port):
    url_key = f"traefik/http/services/srs/loadBalancer/servers/{port}/url"
    url_value = f"http://{address}:{port}"
    etcd.put(url_key, url_value)

def del_traefik_srs_url(etcd, port):
    url_key = f"traefik/http/services/srs/loadBalancer/servers/{port}/url"
    etcd.transaction(
      compare=[
        etcd.transactions.value("__invalid__") == "__invalid__" 
      ], 
      success=[],
      failure=[
        etcd.transactions.delete(url_key)
      ], )
    print(etcd.get(url_key))


closers = [] 

# single request server
async def srs(etcd, port=None):

    # create app handler and closing event
    closing_time = asyncio.Event()
    closing_task = asyncio.create_task(closing_time.wait())
    closers.append(closing_time)
    handler = ClosingHandler(closing_time)

    # add handler to new application runner
    app = web.Application()
    app.add_routes([web.get("/", handler.default)])
    runner = web.AppRunner(app)
    await runner.setup()

    # start app on a specific tcp port
    if port == None:
      port = find_free_port()
    site = web.TCPSite(runner, "localhost", port)
    print(f"{site.name} starting")
    await site.start()

    # setup traefik access
    add_traefik_srs_url(etcd, "localhost", port)

    # wait for closing event
    await closing_task
    print(f"{site.name} closing")
    del_traefik_srs_url(etcd, port)
    await runner.cleanup()

    await srs(etcd, handler.next_port)


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    etcd = etcd3.client() 
    setup_traefik_srs(etcd)

    for i in range(100):
      loop.create_task(srs(etcd))

    try:
      loop.run_forever()
    except:
      print("\nexiting...")
    finally:
      for c in closers:
        c.set() # useful event to also call runner.cleanup
        etcd.close()
