#!/usr/bin/env python
import argparse
import base64
import json
import os
import subprocess
import uuid

import CloudFlare
import yaml
from retrying import retry

from constants import MAX_RETRIES, BACKOFF_SECS
from util import LOGGER


def get_config_from_env():
    config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
    return yaml.safe_load(config_content)


def get_config_from_file():
    config_path = get_env("COMPONENT_TESTS_CONFIG")
    with open(config_path, 'r') as infile:
        return yaml.safe_load(infile)


def persist_config(config):
    config_path = get_env("COMPONENT_TESTS_CONFIG")
    with open(config_path, 'w') as outfile:
        yaml.safe_dump(config, outfile)


def persist_origin_cert(config):
    origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
    path = config["origincert"]
    with open(path, 'w') as outfile:
        outfile.write(origincert)
    return path


@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
def create_tunnel(config, origincert_path, random_uuid):
    # Delete any previous existing credentials file. If the agent keeps files around (that's the case in Windows) then
    # cloudflared tunnel create will refuse to create the tunnel because it does not want to overwrite credentials
    # files.
    credentials_path = config["credentials_file"]
    try:
        os.remove(credentials_path)
    except OSError:
        pass

    tunnel_name = "cfd_component_test-" + random_uuid
    create_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "create",
                  "--credentials-file", credentials_path, tunnel_name]
    LOGGER.info(f"Creating tunnel with {create_cmd}")
    subprocess.run(create_cmd, check=True)

    list_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "list", "--name",
                tunnel_name, "--output", "json"]
    LOGGER.info(f"Listing tunnel with {list_cmd}")
    cloudflared = subprocess.run(list_cmd, check=True, capture_output=True)
    return json.loads(cloudflared.stdout)[0]["id"]


@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
def delete_tunnel(config):
    credentials_path = config["credentials_file"]
    delete_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", config["origincert"], "delete",
                  "--credentials-file", credentials_path, "-f", config["tunnel"]]
    LOGGER.info(f"Deleting tunnel with {delete_cmd}")
    subprocess.run(delete_cmd, check=True)


@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
def create_dns(config, hostname, type, content):
    cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN"))
    cf.zones.dns_records.post(
        config["zone_tag"],
        data={'name': hostname, 'type': type, 'content': content, 'proxied': True}
    )


def create_named_dns(config, random_uuid):
    hostname = "named-" + random_uuid + "." + config["zone_domain"]
    create_dns(config, hostname, "CNAME", config["tunnel"] + ".cfargotunnel.com")
    return hostname


@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
def delete_dns(config, hostname):
    cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN"))
    zone_tag = config["zone_tag"]
    dns_records = cf.zones.dns_records.get(zone_tag, params={'name': hostname})
    if len(dns_records) > 0:
        cf.zones.dns_records.delete(zone_tag, dns_records[0]['id'])


def write_file(content, path):
    with open(path, 'w') as outfile:
        outfile.write(content)


def get_env(env_name):
    val = os.getenv(env_name)
    if val is None:
        raise Exception(f"{env_name} is not set")
    return val


def create():
    """
    Creates the necessary resources for the components test to run.
     - Creates a named tunnel with a random name.
     - Creates a random CNAME DNS entry for that tunnel.

    Those created resources are added to the config (obtained from an environment variable).
    The resulting configuration is persisted for the tests to use.
    """
    config = get_config_from_env()
    origincert_path = persist_origin_cert(config)

    random_uuid = str(uuid.uuid4())
    config["tunnel"] = create_tunnel(config, origincert_path, random_uuid)
    config["ingress"] = [
        {
            "hostname": create_named_dns(config, random_uuid),
            "service": "hello_world"
        },
        {
            "service": "http_status:404"
        }
    ]

    persist_config(config)


def cleanup():
    """
    Reads the persisted configuration that was created previously.
    Deletes the resources that were created there.
    """
    config = get_config_from_file()
    delete_tunnel(config)
    delete_dns(config, config["ingress"][0]["hostname"])


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='setup component tests')
    parser.add_argument('--type', choices=['create', 'cleanup'], default='create')
    args = parser.parse_args()

    if args.type == 'create':
        create()
    else:
        cleanup()
