#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (c) 2019, Nate River <vitikc@gmail.com>
# (c) 2020, Simon Baerlocher <s.baerlocher@sbaerlocher.ch>
# 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

DOCUMENTATION = r'''
---
module: vultr_server_baremetal
short_description: Manages baremetal servers on Vultr.
description:
  - Deploy and destroy servers.
version_added: "0.3.0"
author:
 - "Nate River (@vitikc)"
 - "Simon Baerlocher (@sbaerlocher)"
options:
  name:
    description:
      - Name of the server.
    required: true
    aliases: [ label ]
    type: str
  hostname:
    description:
      - The hostname to assign to this server.
    type: str
  os:
    description:
      - The operating system name or ID.
      - Required if the server does not yet exist and is not restoring from a snapshot.
    type: str
  plan:
    description:
      - Plan name or ID to use for the server.
      - Required if the server does not yet exist.
    type: str
  notify_activate:
    description:
      - Whether to send an activation email when the server is ready or not.
      - Only considered on creation.
    type: bool
    default: false
  ipv6_enabled:
    description:
      - Whether to enable IPv6 or not.
    type: bool
  tag:
    description:
      - Tag for the server.
    type: str
  user_data:
    description:
      - User data to be passed to the server.
    type: str
  startup_script:
    description:
      - Name or ID of the startup script to execute on boot.
      - Only considered while creating the server.
    type: str
  ssh_keys:
    description:
      - List of SSH key names or IDs passed to the server on creation.
    aliases: [ ssh_key ]
    type: list
    elements: str
  reserved_ip_v4:
    description:
      - IP address of the floating IP to use as the main IP of this server.
      - Only considered on creation.
    type: str
  region:
    description:
      - Region name or ID the server is deployed into.
      - Required if the server does not yet exist.
    type: str
  state:
    description:
      - State of the server.
    default: present
    choices: [ present, absent ]
    type: str
extends_documentation_fragment:
- ngine_io.vultr.vultr
'''

EXAMPLES = r'''
- name: create server
  ngine_io.vultr.vultr_server_baremetal:
    name: "{{ vultr_server_baremetal_name }}"
    os: Debian 9 x64 (stretch)
    plan: 32768 MB RAM,2x 240 GB SSD,5.00 TB BW
    region: Amsterdam

- name: ensure a server is absent
  ngine_io.vultr.vultr_server_baremetal:
    name: "{{ vultr_server_baremetal_name }}"
    state: absent
'''

RETURN = r'''
---
vultr_api:
  description: Response from Vultr API with a few additions/modification
  returned: success
  type: complex
  contains:
    api_account:
      description: Account used in the ini file to select the key
      returned: success
      type: str
      sample: default
    api_timeout:
      description: Timeout used for the API requests
      returned: success
      type: int
      sample: 60
    api_retries:
      description: Amount of max retries for the API requests
      returned: success
      type: int
      sample: 5
    api_endpoint:
      description: Endpoint used for the API requests
      returned: success
      type: str
      sample: "https://api.vultr.com"
vultr_server_baremetal:
  description: Response from Vultr API with a few additions/modification
  returned: success
  type: complex
  contains:
    id:
      description: ID of the server
      returned: success
      type: str
      sample: 900000
    name:
      description: Name (label) of the server
      returned: success
      type: str
      sample: "ansible-test-baremetal"
    plan:
      description: Plan used for the server
      returned: success
      type: str
      sample: "32768 MB RAM,2x 240 GB SSD,5.00 TB BW"
    allowed_bandwidth_gb:
      description: Allowed bandwidth to use in GB
      returned: success
      type: int
      sample: 1000
    cost_per_month:
      description: Cost per month for the server
      returned: success
      type: float
      sample: 120.00
    current_bandwidth_gb:
      description: Current bandwidth used for the server
      returned: success
      type: int
      sample: 0
    date_created:
      description: Date when the server was created
      returned: success
      type: str
      sample: "2017-04-12 18:45:41"
    default_password:
      description: Password to login as root into the server
      returned: success
      type: str
      sample: "ab81u!ryranq"
    disk:
      description: Information about the disk
      returned: success
      type: str
      sample: "SSD 250 GB"
    v4_gateway:
      description: IPv4 gateway
      returned: success
      type: str
      sample: "203.0.113.1"
    internal_ip:
      description: Internal IP
      returned: success
      type: str
      sample: ""
    region:
      description: Region the server was deployed into
      returned: success
      type: str
      sample: "Amsterdam"
    v4_main_ip:
      description: Main IPv4
      returned: success
      type: str
      sample: "203.0.113.10"
    v4_netmask:
      description: Netmask IPv4
      returned: success
      type: str
      sample: "255.255.255.0"
    os:
      description: Operating system used for the server
      returned: success
      type: str
      sample: "Debian 9 x64"
    pending_charges:
      description: Pending charges
      returned: success
      type: float
      sample: 0.18
    ram:
      description: Information about the RAM size
      returned: success
      type: str
      sample: "32768 MB"
    status:
      description: Status about the deployment of the server
      returned: success
      type: str
      sample: "active"
    tag:
      description: Server tag
      returned: success
      type: str
      sample: "my tag"
    v6_main_ip:
      description: Main IPv6
      returned: success
      type: str
      sample: "2001:DB8:9000::100"
    v6_network:
      description: Network IPv6
      returned: success
      type: str
      sample: "2001:DB8:9000::"
    v6_network_size:
      description:  Network size IPv6
      returned: success
      type: int
      sample: 64
    v6_networks:
      description: Networks IPv6
      returned: success
      type: list
      sample: []
'''

import time
import base64
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text, to_bytes
from ..module_utils.vultr import (
    Vultr,
    vultr_argument_spec,
)


class AnsibleVultrServerBareMetal(Vultr):

    def __init__(self, module):
        super(AnsibleVultrServerBareMetal, self).__init__(module, "vultr_server_baremetal")

        self.server = None
        self.returns = {
            'SUBID': dict(key='id'),
            'label': dict(key='name'),
            'date_created': dict(),
            'allowed_bandwidth_gb': dict(convert_to='int'),
            'current_bandwidth_gb': dict(),
            'default_password': dict(),
            'internal_ip': dict(),
            'disk': dict(),
            'cost_per_month': dict(convert_to='float'),
            'location': dict(key='region'),
            'main_ip': dict(key='v4_main_ip'),
            'network_v4': dict(key='v4_network'),
            'gateway_v4': dict(key='v4_gateway'),
            'os': dict(),
            'pending_charges': dict(convert_to='float'),
            'ram': dict(),
            'plan': dict(),
            'status': dict(),
            'tag': dict(),
            'v6_main_ip': dict(),
            'v6_network': dict(),
            'v6_network_size': dict(),
            'v6_networks': dict(),
        }
        self.server_power_state = None

    def get_startup_script(self):
        return self.query_resource_by_key(
            key='name',
            value=self.module.params.get('startup_script'),
            resource='startupscript',
        )

    def get_os(self):
        return self.query_resource_by_key(
            key='name',
            value=self.module.params.get('os'),
            resource='os',
            use_cache=True
        )

    def get_ssh_keys(self):
        ssh_key_names = self.module.params.get('ssh_keys')
        if not ssh_key_names:
            return []

        ssh_keys = []
        for ssh_key_name in ssh_key_names:
            ssh_key = self.query_resource_by_key(
                key='name',
                value=ssh_key_name,
                resource='sshkey',
                use_cache=True
            )
            if ssh_key:
                ssh_keys.append(ssh_key)
        return ssh_keys

    def get_region(self):
        return self.query_resource_by_key(
            key='name',
            value=self.module.params.get('region'),
            resource='regions',
            use_cache=True
        )

    def get_plan(self):
        return self.query_resource_by_key(
            key='name',
            value=self.module.params.get('plan'),
            resource='plans',
            query_by='list_baremetal',
            use_cache=True
        )

    def get_user_data(self):
        user_data = self.module.params.get('user_data')
        if user_data is not None:
            user_data = to_text(base64.b64encode(to_bytes(user_data)))
        return user_data

    def get_server_user_data(self, server):
        if not server or not server.get('SUBID'):
            return None

        user_data = self.api_query(path="/v1/baremetal/get_user_data?SUBID=%s" % server.get('SUBID'))
        return user_data.get('userdata')

    def get_server(self, refresh=False):
        if self.server is None or refresh:
            self.server = None
            server_list = self.api_query(path="/v1/baremetal/list")
            if server_list:
                for server_id, server_data in server_list.items():
                    if server_data.get('label') == self.module.params.get('name'):
                        self.server = server_data

                        plan = self.query_resource_by_key(
                            key='METALPLANID',
                            value=server_data['METALPLANID'],
                            resource='plans',
                            query_by='list_baremetal',
                            use_cache=True
                        )
                        self.server['plan'] = plan.get('name')

                        os = self.query_resource_by_key(
                            key='OSID',
                            value=int(server_data['OSID']),
                            resource='os',
                            use_cache=True
                        )
                        self.server['os'] = os.get('name')
        return self.server

    def _wait_for_state(self, key='status', state=None):
        time.sleep(1)
        server = self.get_server(refresh=True)
        for s in range(0, 500):
            if state is None and server.get(key):
                break
            elif server.get(key) == state:
                break
            time.sleep(2)
            server = self.get_server(refresh=True)

        # Timed out
        else:
            if state is None:
                msg = "Wait for '%s' timed out" % key
            else:
                msg = "Wait for '%s' to get into state '%s' timed out" % (key, state)
            self.fail_json(msg=msg)
        return server

    def present_server(self, start_server=True):
        server = self.get_server()
        if not server:
            server = self._create_server(server=server)
        else:
            server = self._update_server(server=server, start_server=start_server)
        return server

    def _create_server(self, server=None):
        required_params = [
            'os',
            'plan',
            'region',
        ]
        self.module.fail_on_missing_params(required_params=required_params)

        self.result['changed'] = True
        if not self.module.check_mode:
            data = {
                'DCID': self.get_region().get('DCID'),
                'METALPLANID': self.get_plan().get('METALPLANID'),
                'OSID': self.get_os().get('OSID'),
                'label': self.module.params.get('name'),
                'hostname': self.module.params.get('hostname'),
                'SSHKEYID': ','.join([ssh_key['SSHKEYID'] for ssh_key in self.get_ssh_keys()]),
                'enable_ipv6': self.get_yes_or_no('ipv6_enabled'),
                'notify_activate': self.get_yes_or_no('notify_activate'),
                'tag': self.module.params.get('tag'),
                'reserved_ip_v4': self.module.params.get('reserved_ip_v4'),
                'user_data': self.get_user_data(),
                'SCRIPTID': self.get_startup_script().get('SCRIPTID'),
            }
            self.api_query(
                path="/v1/baremetal/create",
                method="POST",
                data=data
            )
            server = self._wait_for_state(key='status', state='active')
        return server

    def _update_server(self, server=None, start_server=True):

        # Update plan settings
        # server = self._update_plan_setting(server=server, start_server=start_server)

        # User data
        user_data = self.get_user_data()
        server_user_data = self.get_server_user_data(server=server)
        if user_data is not None and user_data != server_user_data:
            self.result['changed'] = True
            self.result['diff']['before']['user_data'] = server_user_data
            self.result['diff']['after']['user_data'] = user_data

            if not self.module.check_mode:
                data = {
                    'SUBID': server['SUBID'],
                    'userdata': user_data,
                }
                self.api_query(
                    path="/v1/baremetal/set_user_data",
                    method="POST",
                    data=data
                )

        # Tags
        tag = self.module.params.get('tag')
        if tag is not None and tag != server.get('tag'):
            self.result['changed'] = True
            self.result['diff']['before']['tag'] = server.get('tag')
            self.result['diff']['after']['tag'] = tag

            if not self.module.check_mode:
                data = {
                    'SUBID': server['SUBID'],
                    'tag': tag,
                }
                self.api_query(
                    path="/v1/baremetal/tag_set",
                    method="POST",
                    data=data
                )
        return server

    def absent_server(self):
        server = self.get_server()
        if server:
            self.result['changed'] = True
            self.result['diff']['before']['id'] = server['SUBID']
            self.result['diff']['after']['id'] = ""
            if not self.module.check_mode:
                data = {
                    'SUBID': server['SUBID']
                }
                self.api_query(
                    path="/v1/baremetal/destroy",
                    method="POST",
                    data=data
                )
                for s in range(0, 60):
                    if server is not None:
                        break
                    time.sleep(2)
                    server = self.get_server(refresh=True)
                else:
                    self.fail_json(msg="Wait for server '%s' to get deleted timed out" % server['label'])
        return server


def main():
    argument_spec = vultr_argument_spec()
    argument_spec.update(dict(
        name=dict(required=True, aliases=['label']),
        hostname=dict(type='str',),
        os=dict(type='str',),
        plan=dict(type='str',),
        notify_activate=dict(type='bool', default=False),
        ipv6_enabled=dict(type='bool'),
        tag=dict(type='str',),
        reserved_ip_v4=dict(type='str',),
        startup_script=dict(type='str',),
        user_data=dict(type='str',),
        ssh_keys=dict(type='list', elements='str', aliases=['ssh_key']),
        region=dict(type='str',),
        state=dict(type='str', choices=['present', 'absent'], default='present'),
    ))

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
    )

    vultr_server_baremetal = AnsibleVultrServerBareMetal(module)
    if module.params.get('state') == "absent":
        server = vultr_server_baremetal.absent_server()
    else:
        server = vultr_server_baremetal.present_server()

    result = vultr_server_baremetal.get_result(server)
    module.exit_json(**result)


if __name__ == '__main__':
    main()
