"""
Mini-utility to generate docker-compose.yaml file to launch 
workspace on remote server with auth and https (self-signed)

python remote.py --workspace="base-workspace" --port="8020" --host="68.183.69.198" --user="user1" --password="pass1"
"""
import os
import yaml
import shutil
import argparse
import textwrap
import subprocess

# How much tool port is away from the start_port
port_increments = {
    "DOCS_URL": 0,
    "FILEBROWSER_URL": 1,
    "STATICFS_URL": 2,
    "CRONICLE_URL": 3,
    "UNGIT_URL": 4,
    "IDE_URL": 5,
    "TERMINAL_URL": 6,
    "MC_URL": 7,
    "HTOP_URL": 8,
    "ANSIBLE_ARA": 9
    }

workspace_meta = {
    "ubuntu-workspace": {
        "port-range": 10,
        "entrypoints": []
    },
    "base-workspace": {
        "port-range": 10,
        "entrypoints": ["DOCS_URL", "FILEBROWSER_URL", "STATICFS_URL", "CRONICLE_URL", "UNGIT_URL", "TERMINAL_URL", "MC_URL", "HTOP_URL"]
    },
    "workspace-in-docker": {
        "port-range": 15,
        "entrypoints": ["DOCS_URL", "FILEBROWSER_URL", "STATICFS_URL", "CRONICLE_URL", "UNGIT_URL", "IDE_URL", "TERMINAL_URL", "MC_URL", "HTOP_URL"]
    },
    "codeserver-workspace": {
        "port-range": 15,
        "entrypoints": ["DOCS_URL", "FILEBROWSER_URL", "STATICFS_URL", "CRONICLE_URL", "UNGIT_URL", "IDE_URL", "TERMINAL_URL", "MC_URL", "HTOP_URL"]
    },
    "python-workspace": {
        "port-range": 15,
        "entrypoints": ["DOCS_URL", "FILEBROWSER_URL", "STATICFS_URL", "CRONICLE_URL", "UNGIT_URL", "IDE_URL", "TERMINAL_URL", "MC_URL", "HTOP_URL"]
    },
    "mkdocs-magicspace": {
        "port-range": 15,
        "entrypoints": ["DOCS_URL", "FILEBROWSER_URL", "STATICFS_URL", "CRONICLE_URL", "UNGIT_URL", "IDE_URL", "TERMINAL_URL", "MC_URL", "HTOP_URL"]
    },
    "ansible-terraform-workspace": {
        "port-range": 15,
        "entrypoints": ["DOCS_URL", "FILEBROWSER_URL", "STATICFS_URL", "CRONICLE_URL", "UNGIT_URL", "IDE_URL", "TERMINAL_URL", "MC_URL", "HTOP_URL", "ANSIBLE_ARA"]
    }
}


def gen_certs():
    """ Generate self-signed TLS certtificate
    """
    os.mkdir("./remote/certs")
    cmd = 'cd ./remote/certs && openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=workspace.com" -keyout cert.key  -out cert.crt'
    subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
    return


def traefik_config():
    conconf = """
    tls:
      certificates:
        - certFile: /tools/certs/cert.crt
          keyFile: /tools/certs/cert.key
    """
    with open("./remote/config.yml", "a") as f:
        f.write(textwrap.dedent(conconf))
    return
    

def calc_entypoints(workspace_name, start_port):
    """ identify which of the port ranges are taken by the internal 
    applications, and which are free. Return list of all entrypoints
    """
    try:
        workspace_entrypoints = workspace_meta[workspace_name]["entrypoints"]
    except:
        workspace_entrypoints = workspace_meta['workspace-in-docker']["entrypoints"]
    try:
        workspace_port_range = workspace_meta[workspace_name]["port-range"]
    except:
        workspace_port_range = workspace_meta['workspace-in-docker']["port-range"]
    end_port = start_port + workspace_port_range
    internal_end_port = 8020 + workspace_port_range
    # Dict of entrypoints of entrypoint name and port
    ep = {entrypoint:port+start_port for entrypoint,port in port_increments.items() if entrypoint in workspace_entrypoints}
    free_ports_start = 8020 + len(ep) + 1
    free_range = list(range(free_ports_start, 8020 + workspace_port_range + 1))
    free_ep = {"PORT_"+str(port):port for port in free_range}
    ep.update(free_ep)
    port_mapping = f"{start_port}-{end_port}:{8020}-{internal_end_port}"
    return ep, port_mapping



def get_workspace_labels(ep, auth_mid_name="basic-auth"):
    """ Create list of Traefik labels for the Workspace service
    """
    labels = [
        "traefik.enable=true",
        "traefik.http.middlewares.httprepl.redirectregex.regex=^http://(.*)",
        "traefik.http.middlewares.httprepl.redirectregex.replacement=https://$${1}"
        ]
    for e,p in ep.items():
        eplabs = [
            f"traefik.http.services.{e}http.loadbalancer.server.port={p}",
            f"traefik.http.routers.{e}http.service={e}",
            f"traefik.http.routers.{e}http.rule=PathPrefix(`/`)",
            f"traefik.http.routers.{e}http.entrypoints={e}",
            f"traefik.http.routers.{e}http.middlewares=httprepl",

            f"traefik.http.services.{e}.loadbalancer.server.port={p}",
            f"traefik.http.routers.{e}.service={e}",
            f"traefik.http.routers.{e}.rule=PathPrefix(`/`)",
            f"traefik.http.routers.{e}.entrypoints={e}",
            f"traefik.http.routers.{e}.middlewares={auth_mid_name}",
            f"traefik.http.routers.{e}.tls=true"
        ]
        labels.extend(eplabs)
    return labels


def make_authlabels(user, password, auth_mid_name="basic-auth"):
    """ Create Traefik label for authentication
    """
    auth_type = "basicauth"
    cmd = f"echo $(echo '{password}' | htpasswd -nB -i {user}) | sed -e s/\\\$/\\\$\\\$/g"
    result = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read() 
    auth = result.decode("utf-8").replace("\n", "")
    authlabel = f"traefik.http.middlewares.{auth_mid_name}.{auth_type}.users={auth}"
    authlabels = [authlabel]
    return authlabels
    

def get_compose_dict(workspace_name, host_ip, start_port, user, password, custom_image=None):
    """ Create dict of values for docker-compose. This dict is 
    to be transformed into docker-compose.yaml
    """
    # Dict of entrypoints of entrypoint name and port
    ep, port_mapping = calc_entypoints(workspace_name, start_port)
    traefik_command = [f"--entrypoints.{entrypoint}.address=:{port}" for entrypoint,port in ep.items()]
    traefik_command += [
        "--providers.docker",
        "--providers.file.directory=/etc/traefik/dynamic_conf"
        ]
    # Create dict with Traefik values
    y = {}
    y["version"] = "3.3"
    y["services"] = {}
    y["services"]["traefik"] = {}
    y["services"]["traefik"]["image"] = "traefik:v2.4" 
    y["services"]["traefik"]["container_name"] = "container_name"
    y["services"]["traefik"]["command"] = traefik_command
    y["services"]["traefik"]["ports"] = [port_mapping]
    y["services"]["traefik"]["volumes"] = [
        "/var/run/docker.sock:/var/run/docker.sock:ro", 
        "./certs:/tools/certs",
        "./config.yml:/etc/traefik/dynamic_conf/conf.yml:ro"
        ]
    # Add Workspace values to the dict
    full_image = f"alnoda/{workspace_name}"
    if custom_image is not None:
        full_image = custom_image
    y["services"]["workspace"] = {}
    y["services"]["workspace"]["image"] = full_image
    y["services"]["workspace"]["environment"] = {
        "WRK_HOST": host_ip, 
        "ENTRY_PORT": start_port,
        "WRK_PROTO": "https",
        "ARA_API_SERVER": f"http://{host_ip}:{start_port + port_increments['ANSIBLE_ARA']}",
        "ARA_API_CLIENT": "https",
        "ARA_CORS_ORIGIN_WHITELIST": f"['https://{host_ip}', 'http://{host_ip}']",
        "ARA_ALLOWED_HOSTS": f"['127.0.0.', 'localhost', '::1', '{host_ip}']",
        "ARA_EXTERNAL_AUTH": "True"
        }
    y["services"]["workspace"]["labels"] = get_workspace_labels(ep)
    # Add auth
    authlabels = make_authlabels(user, password)
    y["services"]["workspace"]["labels"].extend(authlabels)
    return y
    

def main(cmd_args):
    """ Create folder with everything needed to 
    spin up workspace with auth and TLS on remote server
    """
    workspace_name = cmd_args.workspace
    host_ip = cmd_args.host
    start_port = int(cmd_args.port)
    user = cmd_args.user
    password = cmd_args.password
    custom_image = cmd_args.image
    try: 
        shutil.rmtree("./remote")
    except: 
        pass
    os.mkdir("./remote")
    # Generate certificate
    gen_certs()
    # Create Traefik config
    traefik_config()
    # Create docker-compose file
    comp_dict = get_compose_dict(workspace_name, host_ip, start_port, user, password, custom_image)
    with open("./remote/docker-compose.yaml", "a") as y:
        y.write(yaml.dump(comp_dict, default_style='"'))
    return


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--workspace", default="workspace-in-docker")
    parser.add_argument("--port", default=8020)
    parser.add_argument("--host")
    parser.add_argument("--user")
    parser.add_argument("--password")
    parser.add_argument("--image", default=None)
    cmd_args = parser.parse_args()
    main(cmd_args)
