#!/usr/bin/env python

from functools import lru_cache
from getpass import getpass
import os
from pathlib import Path
import subprocess
import sys
import json

from ansible.module_utils.six.moves import configparser
from ansible.plugins.vars import BaseVarsPlugin


DOCUMENTATION = """
    module: pass
    vars: vault
    version_added: 2.9
    short_description: Load vault passwords from pass
    description:
        - Works exactly as a vault, loading variables from pass.
        - Decrypts the YAML file `ansible_vault` from cranspasswords.
        - Loads the secret variables.
        - Makes use of data caching in order to avoid calling cranspasswords multiple times.
        - Uses the local gpg key from the user running ansible on the Control node.
"""



class VarsModule(BaseVarsPlugin):
    @staticmethod
    @lru_cache
    def decrypt_password(name, crans_submodule=False):
        """
        Passwords are decrypted from the local password store, then are cached.
        By that way, we don't decrypt these passwords everytime.
        """
        # Load config
        config = configparser.ConfigParser()
        config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pass.ini'))

        password_store = Path(config.get('pass', 'password_store_dir',
            fallback=os.getenv('PASSWORD_STORE_DIR', Path.home() / '.password-store')))

        if crans_submodule:
            password_store /= config.get('pass', 'crans_password_store_submodule',
                    fallback=os.getenv('CRANS_PASSWORD_STORE_SUBMODULE', 'crans'))
        full_command = ['gpg', '-q', '-d', password_store / f'{name}.gpg']
        proc = subprocess.run(full_command, capture_output=True, close_fds=True)
        clear_text = proc.stdout.decode('UTF-8')
        sys.stderr.write(proc.stderr.decode('UTF-8'))
        return clear_text

    @staticmethod
    @lru_cache
    def become_password(entity):
        """
        Query the become password that should be used for the given entity.
        If entity is the whole group that has no default password,
        the become password will be prompted.
        The configuration should be given in pass.ini, in the `pass_become`
        group. You have only to write `group=pass-filename`.
        """
        # Load config
        config = configparser.ConfigParser()
        config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pass.ini'))
        if config.has_option('pass_become', entity.get_name()):
            return VarsModule.decrypt_password(
                    config.get('pass_become', entity.get_name())).split('\n')[0]
        if entity.get_name() == "all":
            return getpass("BECOME password: ", stream=None)
        return None

    def get_vars(self, loader, path, entities):
        """
        Get all vars for entities, called by Ansible.

        loader: Ansible's DataLoader.
        path: Current play's playbook directory.
        entities: Host or group names pertinent to the variables needed.
        """
        # VarsModule objects are called every time you need host vars, per host,
        # and per group the host is part of.
        # It is about 6 times per host per task in current state
        # of Ansible Crans configuration.

        # It is way to much.
        # So we cache the data into the DataLoader (see parsing/DataLoader).

        passwords = {}

        config = configparser.ConfigParser()
        config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pass.ini'))

        password_store = Path(config.get('pass', 'password_store_dir',
            fallback=os.getenv('PASSWORD_STORE_DIR', Path.home() / '.password-store')))

        password_store /= config.get('pass', 'crans_password_store_submodule',
            fallback=os.getenv('CRANS_PASSWORD_STORE_SUBMODULE', 'crans'))

        password_store /= '.last_group.json'

        with open(password_store) as file:
            files = json.load(file)

        files = [ file for file in files if file.startswith('ansible/') ]

        for entity in entities:
            # Load vault passwords
            if entity.get_name() == 'all':
                passwords['vault'] = {}
                for file in files:
                    paths = file.removeprefix('ansible/').split('/')
                    d = passwords['vault']
                    for path in paths[:-1]:
                        if path not in d:
                            d[path] = {}
                        d = d[path]
                    try:
                        d[paths[-1]] = loader.load(VarsModule.decrypt_password(file, True))
                    except Exception as e:
                        print(file)

            # Load become password
            become_password = VarsModule.become_password(entity)
            if become_password is not None:
                passwords['ansible_become_password'] = become_password

        return passwords