#!/usr/bin/env python3

import argparse
import os, re, subprocess, sys, yaml
import shutil
import textwrap

p = argparse.ArgumentParser(
    description="""
        Creates links from the cluster directory into the tpa directory
    """,
    usage=textwrap.dedent(
        """
        tpaexec relink clustername [--force]

        Tries to repair any dangling or missing symbolic links within a cluster
        directory (e.g., deploy.yml) by making them point to the correct
        $TPA_DIR when appropriate. (TPA_DIR=%s)

        This command is useful if a cluster was configured on one machine and
        moved to another system with TPA installed in a different location,
        or to add links for new commands.

        If the cluster is configured for use with Ansible Automation Platform,
        copies files instead of creating symlinks.

        With --force, removes links or files if they already exist.

                          """
    )
    % os.environ.get("TPA_DIR", "<unset>"),
)
p.add_argument("--force", help="remove existing files", action="store_true")
p.add_argument("--verbose", "-v", action="store_true")

args = vars(p.parse_args())
v = args.get("verbose")

# First, we establish that we were invoked as ``tpaexec relink clusterdir`` on a
# valid cluster directory.

if "TPA_DIR" not in os.environ or not os.path.exists("./config.yml"):
    print(
        "ERROR: please invoke as 'tpaexec relink /path/to/cluster/dir'", file=sys.stderr
    )
    sys.exit(-1)

TPA_DIR = os.environ["TPA_DIR"]

modified_paths = []

# Given the name of a symbolic link and the missing target of the link, this
# function tries to point it to $TPA_DIR.


def repair_dangling_symlink(link, target):
    # We can't repair links that don't point to TPA_DIR
    if not re.findall("/(TPA|tpaexec)/", target):
        if v:
            print("%s: <ignored; unrecognised target>" % link)
        return

    new_target = re.sub("^.*/(TPA|tpaexec)/", "", target)

    for old_bdr_arch in ["CAMO2x2", "BDR-Simple"]:
        if new_target.startswith(f"architectures/{old_bdr_arch}/"):
            new_target = re.sub(old_bdr_arch, "BDR-Always-ON", new_target)

    new_target = os.path.join(TPA_DIR, new_target)
    if os.path.exists(new_target):
        os.unlink(link)
        os.symlink(new_target, link)
        if v:
            print("%s: %s => %s" % (link, target, new_target))
    else:
        os.unlink(link)
        if v:
            print("%s: <removed>" % link)


# Now we can look for dangling symlinks somewhere under the current directory
# and try to repair them.

for subdir, dirs, files in os.walk("."):
    for f in files:
        file = os.path.join(subdir, f)
        if os.path.islink(file):
            target = os.readlink(file)
            if not os.path.exists(target):
                repair_dangling_symlink(file, target)
                modified_paths.append(file)

# Create links to any new commands or tests for this cluster's architecture.
# (The cluster configuration may have been generated long before the current
# TPA release.)

with open(r"./config.yml") as config_file:
    try:
        config = yaml.safe_load(config_file)
        architecture = config["architecture"]
    except:
        sys.exit("Failed to read 'architecture' from config.yml")

    # In theory, the architecture may return something else from
    # links_to_create(), but we ignore that possibility for now.

    for s in ["deploy.yml", "commands", "tests"]:
        target = "%s/architectures/%s/%s" % (TPA_DIR, architecture, s)

        if os.path.isfile(target):
            if args.get("force") and os.path.isfile(s):
                os.remove(s)
            if not os.path.exists(s):
                # copy file for tower use case
                # to ensure file content is passed in git repo
                # instead of symlink path
                if config.get("ansible_tower"):
                    shutil.copy(target, s)
                else:
                    os.symlink(target, s)
                modified_paths.append(s)
                if v:
                    print("%s: %s" % (s, target))

        elif os.path.isdir(target):
            if not os.path.exists(s):
                os.mkdir(s)

            for l in os.listdir(target):
                link = "%s/%s" % (s, l)
                dest = "%s/%s" % (target, l)
                if args.get("force") and os.path.isfile(link):
                    os.remove(link)
                if not os.path.exists(link):
                    # copy file for tower use case
                    # to ensure file content is passed in git repo
                    # instead of symlink path
                    if config.get("ansible_tower"):
                        shutil.copy(dest, link)
                    else:
                        os.symlink(dest, link)
                    modified_paths.append(link)
                    if v:
                        print("%s: %s" % (link, dest))

if os.path.exists(".git") and modified_paths:
    try:
        cp = subprocess.run(
            "git notes show $(git rev-list --reverse HEAD | head -1)",
            shell=True,
            stdout=subprocess.PIPE,
            universal_newlines=True,
        )
        if cp.stdout.rstrip() == "Created by TPA":
            cp = subprocess.run(
                ["git", "add", *modified_paths],
                stdout=subprocess.PIPE,
                universal_newlines=True,
            )
            if v:
                print(cp.stdout)
            cp = subprocess.run(
                ["git", "commit", "-m", "Relink files to the correct $TPA_DIR"],
                stdout=subprocess.PIPE,
                universal_newlines=True,
            )
            if v:
                print(cp.stdout)
    except subprocess.CalledProcessError as cpe:
        print("Failed to git commit: %s" % cpe.stderr)
