#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2017, Google Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
---
module: gcspanner
version_added: "2.3"
short_description: Create and Delete Instances/Databases on Spanner
description:
    - Create and Delete Instances/Databases on Spanner.
      See U(https://cloud.google.com/spanner/docs) for an overview.
requirements:
  - python >= 2.6
  - google-auth >= 0.5.0
  - google-cloud-spanner >= 0.23.0
notes:
  - Changing the configuration on an existing instance is not supported.
author:
  - Tom Melendez (@supertom) <tom@supertom.com>
options:
  configuration:
    description:
       - Configuration the instance should use.
       - Examples are us-central1, asia-east1 and europe-west1.
    required: yes
  instance_id:
    description:
       - GCP spanner instance name.
    required: yes
  database_name:
    description:
       - Name of database contained on the instance.
  force_instance_delete:
    description:
       - To delete an instance, this argument must exist and be true (along with state being equal to absent).
    type: bool
    default: 'no'
  instance_display_name:
    description:
       - Name of Instance to display.
       - If not specified, instance_id will be used instead.
  node_count:
    description:
       - Number of nodes in the instance.
    default: 1
  state:
    description:
    - State of the instance or database. Applies to the most granular resource.
    - If a C(database_name) is specified we remove it.
    - If only C(instance_id) is specified, that is what is removed.
    choices: [ absent, present ]
    default: present
'''

EXAMPLES = '''
- name: Create instance
  gcspanner:
    instance_id: '{{ instance_id }}'
    configuration: '{{ configuration }}'
    state: present
    node_count: 1

- name: Create database
  gcspanner:
    instance_id: '{{ instance_id }}'
    configuration: '{{ configuration }}'
    database_name: '{{ database_name }}'
    state: present

- name: Delete instance (and all databases)
- gcspanner:
    instance_id: '{{ instance_id }}'
    configuration: '{{ configuration }}'
    state: absent
    force_instance_delete: yes
'''

RETURN = '''
state:
    description: The state of the instance or database. Value will be either 'absent' or 'present'.
    returned: Always
    type: str
    sample: "present"

database_name:
    description: Name of database.
    returned: When database name is specified
    type: str
    sample: "mydatabase"

instance_id:
    description: Name of instance.
    returned: Always
    type: str
    sample: "myinstance"

previous_values:
   description: List of dictionaries containing previous values prior to update.
   returned: When an instance update has occurred and a field has been modified.
   type: dict
   sample: "'previous_values': { 'instance': { 'instance_display_name': 'my-instance', 'node_count': 1 } }"

updated:
   description: Boolean field to denote an update has occurred.
   returned: When an update has occurred.
   type: bool
   sample: True
'''
try:
    from ast import literal_eval
    HAS_PYTHON26 = True
except ImportError:
    HAS_PYTHON26 = False

try:
    from google.cloud import spanner
    from google.gax.errors import GaxError
    HAS_GOOGLE_CLOUD_SPANNER = True
except ImportError as e:
    HAS_GOOGLE_CLOUD_SPANNER = False

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.gcp import check_min_pkg_version, get_google_cloud_credentials
from ansible.module_utils.six import string_types


CLOUD_CLIENT = 'google-cloud-spanner'
CLOUD_CLIENT_MINIMUM_VERSION = '0.23.0'
CLOUD_CLIENT_USER_AGENT = 'ansible-spanner-0.1'


def get_spanner_configuration_name(config_name, project_name):
    config_name = 'projects/%s/instanceConfigs/regional-%s' % (project_name,
                                                               config_name)
    return config_name


def instance_update(instance):
    """
    Call update method on spanner client.

    Note: A ValueError exception is thrown despite the client succeeding.
    So, we validate the node_count and instance_display_name parameters and then
    ignore the ValueError exception.

    :param instance: a Spanner instance object
    :type instance: class `google.cloud.spanner.Instance`

    :returns True on success, raises ValueError on type error.
    :rtype ``bool``
    """
    errmsg = ''
    if not isinstance(instance.node_count, int):
        errmsg = 'node_count must be an integer %s (%s)' % (
            instance.node_count, type(instance.node_count))
    if instance.display_name and not isinstance(instance.display_name,
                                                string_types):
        errmsg = 'instance_display_name must be an string %s (%s)' % (
            instance.display_name, type(instance.display_name))
    if errmsg:
        raise ValueError(errmsg)

    try:
        instance.update()
    except ValueError:
        # The ValueError here is the one we 'expect'.
        pass

    return True


def main():
    module = AnsibleModule(
        argument_spec=dict(
            instance_id=dict(type='str', required=True),
            state=dict(type='str', default='present', choices=['absent', 'present']),
            database_name=dict(type='str'),
            configuration=dict(type='str', required=True),
            node_count=dict(type='int', default=1),
            instance_display_name=dict(type='str'),
            force_instance_delete=dict(type='bool', default=False),
            service_account_email=dict(type='str'),
            credentials_file=dict(type='str'),
            project_id=dict(type='str'),
        ),
    )

    if not HAS_PYTHON26:
        module.fail_json(
            msg="GCE module requires python's 'ast' module, python v2.6+")

    if not HAS_GOOGLE_CLOUD_SPANNER:
        module.fail_json(msg="Please install google-cloud-spanner.")

    if not check_min_pkg_version(CLOUD_CLIENT, CLOUD_CLIENT_MINIMUM_VERSION):
        module.fail_json(msg="Please install %s client version %s" %
                         (CLOUD_CLIENT, CLOUD_CLIENT_MINIMUM_VERSION))

    mod_params = {}
    mod_params['state'] = module.params.get('state')
    mod_params['instance_id'] = module.params.get('instance_id')
    mod_params['database_name'] = module.params.get('database_name')
    mod_params['configuration'] = module.params.get('configuration')
    mod_params['node_count'] = module.params.get('node_count', None)
    mod_params['instance_display_name'] = module.params.get('instance_display_name')
    mod_params['force_instance_delete'] = module.params.get('force_instance_delete')

    creds, params = get_google_cloud_credentials(module)
    spanner_client = spanner.Client(project=params['project_id'],
                                    credentials=creds,
                                    user_agent=CLOUD_CLIENT_USER_AGENT)
    changed = False
    json_output = {}

    i = None
    if mod_params['instance_id']:
        config_name = get_spanner_configuration_name(
            mod_params['configuration'], params['project_id'])
        i = spanner_client.instance(mod_params['instance_id'],
                                    configuration_name=config_name)
    d = None
    if mod_params['database_name']:
        # TODO(supertom): support DDL
        ddl_statements = ''
        d = i.database(mod_params['database_name'], ddl_statements)

    if mod_params['state'] == 'absent':
        # Remove the most granular resource.  If database is specified
        # we remove it.  If only instance is specified, that is what is removed.
        if d is not None and d.exists():
            d.drop()
            changed = True
        else:
            if i.exists():
                if mod_params['force_instance_delete']:
                    i.delete()
                else:
                    module.fail_json(
                        msg=(("Cannot delete Spanner instance: "
                              "'force_instance_delete' argument not specified")))
                changed = True
    elif mod_params['state'] == 'present':
        if not i.exists():
            i = spanner_client.instance(mod_params['instance_id'],
                                        configuration_name=config_name,
                                        display_name=mod_params['instance_display_name'],
                                        node_count=mod_params['node_count'] or 1)
            i.create()
            changed = True
        else:
            # update instance
            i.reload()
            inst_prev_vals = {}
            if i.display_name != mod_params['instance_display_name']:
                inst_prev_vals['instance_display_name'] = i.display_name
                i.display_name = mod_params['instance_display_name']
            if mod_params['node_count']:
                if i.node_count != mod_params['node_count']:
                    inst_prev_vals['node_count'] = i.node_count
                    i.node_count = mod_params['node_count']
            if inst_prev_vals:
                changed = instance_update(i)
                json_output['updated'] = changed
                json_output['previous_values'] = {'instance': inst_prev_vals}
        if d:
            if not d.exists():
                d.create()
                d.reload()
                changed = True

    json_output['changed'] = changed
    json_output.update(mod_params)
    module.exit_json(**json_output)


if __name__ == '__main__':
    main()
