#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2021, Brian Scholer (@briantist)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = """
  module: vault_login
  version_added: 2.2.0
  author:
    - Brian Scholer (@briantist)
  short_description: Perform a login operation against HashiCorp Vault
  requirements:
    - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html))
    - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements).
  description:
    - Performs a login operation against a given path in HashiCorp Vault, returning the login response, including the token.
  seealso:
    - ref: community.hashi_vault.vault_login lookup <ansible_collections.community.hashi_vault.vault_login_lookup>
      description: The official documentation for the C(community.hashi_vault.vault_login) lookup plugin.
    - ref: community.hashi_vault.vault_login_token filter <ansible_collections.community.hashi_vault.docsite.filter_guide.vault_login_token>
      description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin.
  extends_documentation_fragment:
    - community.hashi_vault.connection
    - community.hashi_vault.auth
  notes:
    - "A login is a write operation (creating a token persisted to storage), so this module always reports C(changed=True),
      except when used with C(token) auth, because no new token is created in that case. For the purposes of Ansible playbooks however,
      it may be more useful to set C(changed_when=false) if you're doing idempotency checks against the target system."
    - The C(none) auth method is not valid for this module because there is no response to return.
    - "With C(token) auth, no actual login is performed.
      Instead, the given token's additional information is returned in a structure that resembles what login responses look like."
    - "The C(token) auth method will only return full information if I(token_validate=True).
      If the token does not have the C(lookup-self) capability, this will fail. If I(token_validate=False), only the token value itself
      will be returned in the structure."
    - "In check mode, this module will not perform a login, and will instead return a basic structure with an empty token.
      However this may not be useful if the token is required for follow on tasks.
      It may be better to use this module with C(check_mode=no) in order to have a valid token that can be used."
  options:
    token_validate:
      description:
        - For token auth, will perform a C(lookup-self) operation to determine the token's validity before using it.
        - Disable if your token does not have the C(lookup-self) capability.
      default: true
"""
# TODO: remove token_validate description in 4.0.0 when it will match the doc frag description.

EXAMPLES = """
- name: Login and use the resulting token
  community.hashi_vault.vault_login:
    url: https://vault:8201
    auth_method: userpass
    username: user
    password: '{{ passwd }}'
  register: login_data

- name: Retrieve an approle role ID (token via filter)
  community.hashi_vault.vault_read:
    url: https://vault:8201
    auth_method: token
    token: '{{ login_data | community.hashi_vault.vault_login_token }}'
    path: auth/approle/role/role-name/role-id
  register: approle_id

- name: Retrieve an approle role ID (token via direct dict access)
  community.hashi_vault.vault_read:
    url: https://vault:8201
    auth_method: token
    token: '{{ login_data.login.auth.client_token }}'
    path: auth/approle/role/role-name/role-id
  register: approle_id
"""

RETURN = """
login:
  description: The result of the login against the given auth method.
  returned: success
  type: dict
  contains:
    auth:
      description: The C(auth) member of the login response.
      returned: success
      type: dict
      contains:
        client_token:
          description: Contains the token provided by the login operation (or the input token when I(auth_method=token)).
          returned: success
          type: str
    data:
      description: The C(data) member of the login response.
      returned: success, when available
      type: dict
"""

import traceback

from ansible.module_utils._text import to_native
from ansible.module_utils.basic import missing_required_lib

from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError

# we don't actually need to import hvac directly in this module
# because all of the hvac calls happen in module utils, but
# we would like to control the error message here for consistency.
try:
    import hvac
except ImportError:
    HAS_HVAC = False
    HVAC_IMPORT_ERROR = traceback.format_exc()
else:
    HVAC_IMPORT_ERROR = None
    HAS_HVAC = True


def run_module():
    argspec = HashiVaultModule.generate_argspec(
        # we override this from the shared argspec in order to turn off no_log
        # otherwise we would not be able to return the input token value
        token=dict(type='str', no_log=False, default=None),

        # we override this from the shared argspec because the default for
        # this module should be True, which will differ from the rest of the
        # collection after 4.0.0.
        token_validate=dict(type='bool', default=True)
    )

    module = HashiVaultModule(
        argument_spec=argspec,
        supports_check_mode=True
    )

    if not HAS_HVAC:
        module.fail_json(
            msg=missing_required_lib('hvac'),
            exception=HVAC_IMPORT_ERROR
        )

    # a login is technically a write operation, using storage and resources
    changed = True
    auth_method = module.params.get('auth_method')

    if auth_method == 'none':
        module.fail_json(msg="The 'none' auth method is not valid for this module.")

    if auth_method == 'token':
        # with the token auth method, we don't actually perform a login operation
        # nor change the state of Vault; it's read-only (to lookup the token's info)
        changed = False

    module.connection_options.process_connection_options()
    client_args = module.connection_options.get_hvac_connection_options()
    client = module.helper.get_vault_client(**client_args)

    try:
        module.authenticator.validate()
        if module.check_mode:
            response = {'auth': {'client_token': None}}
        else:
            response = module.authenticator.authenticate(client)
    except (NotImplementedError, HashiVaultValueError) as e:
        module.fail_json(msg=to_native(e), exception=traceback.format_exc())

    module.exit_json(changed=changed, login=response)


def main():
    run_module()


if __name__ == '__main__':
    main()
