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

import argparse
import os
import sys
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 Exception:
    raise PasswordWriteError(
        f"vault_password_file: {args.get('vault_password_file')} not found"
    ) from None

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)

password_file = None
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", f"{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", encoding="utf-8") as stream:
        stream.write(
            yaml.dump(
                {secret.get_name(): secret},
                Dumper=get_dumper(),
            ),
        )
except Exception:
    raise PasswordWriteError(
        f"Could not write password in file {password_file}"
    ) from None
