#!/usr/bin/env python
# © Copyright EnterpriseDB UK Limited 2015-2024 - All rights reserved.

import argparse
import os
import sys
from posixpath import basename, join
from getpass import getpass

from ansible.cli import CLI
from ansible.parsing.dataloader import DataLoader
from ansible import constants as C
from ansible.parsing.vault import VaultLib

import yaml
from tpaexec.password import generate_password
from tpaexec.exceptions import PasswordWriteError


# Class to teach YAML how to parse secret as vaulted var
class Secret:
    """Secret class."""

    def __init__(self, name, secret):
        self._name, self._secret = name, secret

    def __str__(self):
        """Override to_string method."""
        return f"Secret(name={self._name}, secret=***)"

    def get_value(self):
        """Get secret value."""
        return self._secret

    def get_name(self):
        """Get secret name."""
        return self._name


# Argument parsing
prog = "store-password"
p = argparse.ArgumentParser(
    prog=prog,
    description="""
                Stores an encrypted new password for the given user in the inventory.

                Prompts you to enter a new password by default, but will generate and
                use a random password instead if you specify --random.

                Note: the new password is only stored in the local inventory. It will be
                changed on the target instances (if needed) only during the next deploy.
            """,
    formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument(
    "user",
    help="user name associated to the password",
)
p.add_argument(
    "--vault_password_file",
    help="path to vault password file",
)
p.add_argument(
    "--random",
    help="generate a random password",
    action="store_true",
)
args = vars(p.parse_args())

# setup vault informations
try:
    loader = DataLoader()
    vault_secrets = CLI.setup_vault_secrets(
        loader,
        vault_ids=C.DEFAULT_VAULT_IDENTITY_LIST,
        vault_password_files=[args.get("vault_password_file")],
        create_new_password=True,
    )
    vault = VaultLib(vault_secrets)
except:
    raise PasswordWriteError(
        "vault_password_file: {} not found".format(args.get("vault_password_file"))
    )

secret = Secret(
    args.get("user") + "_password",
    vault.encrypt(generate_password()).decode("utf8")
    if args.get("random", False)
    else vault.encrypt(getpass()).decode("utf8"),
)
group_vars_dir = "inventory/group_vars"
if not (os.path.exists(group_vars_dir) and os.path.isdir(group_vars_dir)):
    sys.stderr.write("Failed to find inventory at " + group_vars_dir)
    sys.exit(1)

for entry in os.listdir(group_vars_dir):
    if entry.startswith("tag_Cluster_"):
        password_file = os.path.join(
            group_vars_dir, entry, "secrets", secret.get_name() + ".yml"
        )


# Teach YAML how to format ansible vault encrypted vars
def secret_representer(
    dumper: yaml.SafeDumper, secret: Secret
) -> yaml.nodes.MappingNode:
    """Represent secret instance as a YAML mapping node."""
    return dumper.represent_scalar("!vault", "%s" % secret.get_value(), style="|")


def get_dumper():
    """Add representers to a YAML serializer."""
    safe_dumper = yaml.SafeDumper
    safe_dumper.add_representer(Secret, secret_representer)
    return safe_dumper


# write new secret to file.
try:
    with open(password_file, "w") as stream:
        stream.write(
            yaml.dump(
                {secret.get_name(): secret},
                Dumper=get_dumper(),
            ),
        )
except:
    raise PasswordWriteError(
        "Could not write password in file {}".format(password_file)
    )
