#!/usr/bin/python
#  -*- coding: utf-8 -*-
#  Copyright: (c) 2019, Ansible Project
#  Copyright: (c) 2019, Diane Wang <dianew@vmware.com>
#  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: vmware_guest_screenshot
short_description: Create a screenshot of the Virtual Machine console.
description:
    - This module is used to take screenshot of the given virtual machine when virtual machine is powered on.
    - All parameters and VMware object names are case sensitive.
author:
    - Diane Wang (@Tomorrow9) <dianew@vmware.com>
notes:
    - Tested on vSphere 6.5 and 6.7
requirements:
    - "python >= 2.6"
    - PyVmomi
options:
   name:
     description:
     - Name of the virtual machine.
     - This is a required parameter, if parameter C(uuid) or C(moid) is not supplied.
     type: str
   uuid:
     description:
     - UUID of the instance to gather facts if known, this is VMware's unique identifier.
     - This is a required parameter, if parameter C(name) or C(moid) is not supplied.
     type: str
   moid:
     description:
     - Managed Object ID of the instance to manage if known, this is a unique identifier only within a single vCenter instance.
     - This is required if C(name) or C(uuid) is not supplied.
     type: str
   folder:
     description:
     - Destination folder, absolute or relative path to find an existing guest.
     - This is a required parameter, only if multiple VMs are found with same name.
     - The folder should include the datacenter. ESXi server's datacenter is ha-datacenter.
     - 'Examples:'
     - '   folder: /ha-datacenter/vm'
     - '   folder: ha-datacenter/vm'
     - '   folder: /datacenter1/vm'
     - '   folder: datacenter1/vm'
     - '   folder: /datacenter1/vm/folder1'
     - '   folder: datacenter1/vm/folder1'
     - '   folder: /folder1/datacenter1/vm'
     - '   folder: folder1/datacenter1/vm'
     - '   folder: /folder1/datacenter1/vm/folder2'
     type: str
   cluster:
     description:
     - The name of cluster where the virtual machine is running.
     - This is a required parameter, if C(esxi_hostname) is not set.
     - C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
     type: str
   esxi_hostname:
     description:
     - The ESXi hostname where the virtual machine is running.
     - This is a required parameter, if C(cluster) is not set.
     - C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
     type: str
   datacenter:
     description:
     - The datacenter name to which virtual machine belongs to.
     type: str
   local_path:
     description:
     - 'If C(local_path) is not set, the created screenshot file will be kept in the directory of the virtual machine
       on ESXi host. If C(local_path) is set to a valid path on local machine, then the screenshot file will be
       downloaded from ESXi host to the local directory.'
     - 'If not download screenshot file to local machine, you can open it through the returned file URL in screenshot
       facts manually.'
     type: path
extends_documentation_fragment:
- community.vmware.vmware.documentation

'''

EXAMPLES = r'''
- name: take a screenshot of the virtual machine console
  community.vmware.vmware_guest_screenshot:
    hostname: "{{ vcenter_hostname }}"
    username: "{{ vcenter_username }}"
    password: "{{ vcenter_password }}"
    datacenter: "{{ datacenter_name }}"
    folder: "{{ folder_name }}"
    name: "{{ vm_name }}"
    local_path: "/tmp/"
  delegate_to: localhost
  register: take_screenshot

- name: Take a screenshot of the virtual machine console using MoID
  community.vmware.vmware_guest_screenshot:
    hostname: "{{ vcenter_hostname }}"
    username: "{{ vcenter_username }}"
    password: "{{ vcenter_password }}"
    datacenter: "{{ datacenter_name }}"
    folder: "{{ folder_name }}"
    moid: vm-42
    local_path: "/tmp/"
  delegate_to: localhost
  register: take_screenshot
'''

RETURN = r'''
screenshot_info:
    description: display the facts of captured virtual machine screenshot file
    returned: always
    type: dict
    sample: {
            "virtual_machine": "test_vm",
            "screenshot_file": "[datastore0] test_vm/test_vm-1.png",
            "task_start_time": "2019-05-25T10:35:04.215016Z",
            "task_complete_time": "2019-05-25T10:35:04.412622Z",
            "result": "success",
            "screenshot_file_url": "https://test_vcenter/folder/test_vm/test_vm-1.png?dcPath=test-dc&dsName=datastore0",
            "download_local_path": "/tmp/",
            "download_file_size": 2367,
    }
'''

try:
    from pyVmomi import vim, vmodl
except ImportError:
    pass

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six.moves.urllib.parse import urlencode, quote
from ansible.module_utils._text import to_native
from ansible.module_utils.urls import open_url
from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec, wait_for_task, get_parent_datacenter
import os


class PyVmomiHelper(PyVmomi):
    def __init__(self, module):
        super(PyVmomiHelper, self).__init__(module)
        self.change_detected = False

    def generate_http_access_url(self, file_path):
        # e.g., file_path is like this format: [datastore0] test_vm/test_vm-1.png
        # from file_path generate URL
        url_path = None
        if not file_path:
            return url_path

        path = "/folder/%s" % quote(file_path.split()[1])
        params = dict(dsName=file_path.split()[0].strip('[]'))
        if not self.is_vcenter():
            datacenter = 'ha-datacenter'
        else:
            datacenter = get_parent_datacenter(self.current_vm_obj).name.replace('&', '%26')
        params['dcPath'] = datacenter
        url_path = "https://%s%s?%s" % (self.params['hostname'], path, urlencode(params))

        return url_path

    def download_screenshot_file(self, file_url, local_file_path, file_name):
        response = None
        download_size = 0
        # file is downloaded as local_file_name when specified, or use original file name
        if local_file_path.endswith('.png'):
            local_file_name = local_file_path.split('/')[-1]
            local_file_path = local_file_path.rsplit('/', 1)[0]
        else:
            local_file_name = file_name
        if not os.path.exists(local_file_path):
            try:
                os.makedirs(local_file_path)
            except OSError as err:
                self.module.fail_json(msg="Exception caught when create folder %s on local machine, with error %s"
                                          % (local_file_path, to_native(err)))
        local_file = os.path.join(local_file_path, local_file_name)
        with open(local_file, 'wb') as handle:
            try:
                response = open_url(file_url, url_username=self.params.get('username'),
                                    url_password=self.params.get('password'), validate_certs=False)
            except Exception as err:
                self.module.fail_json(msg="Download screenshot file from URL %s, failed due to %s" % (file_url, to_native(err)))
            if not response or response.getcode() >= 400:
                self.module.fail_json(msg="Download screenshot file from URL %s, failed with response %s, response code %s"
                                          % (file_url, response, response.getcode()))
            bytes_read = response.read(2 ** 20)
            while bytes_read:
                handle.write(bytes_read)
                handle.flush()
                os.fsync(handle.fileno())
                download_size += len(bytes_read)
                bytes_read = response.read(2 ** 20)

        return download_size

    def get_screenshot_facts(self, task_info, file_url, file_size):
        screenshot_facts = dict()
        if task_info is not None:
            screenshot_facts = dict(
                virtual_machine=task_info.entityName,
                screenshot_file=task_info.result,
                task_start_time=task_info.startTime,
                task_complete_time=task_info.completeTime,
                result=task_info.state,
                screenshot_file_url=file_url,
                download_local_path=self.params.get('local_path'),
                download_file_size=file_size,
            )

        return screenshot_facts

    def take_vm_screenshot(self):
        if self.current_vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOn:
            self.module.fail_json(msg="VM is %s, valid power state is poweredOn." % self.current_vm_obj.runtime.powerState)
        try:
            task = self.current_vm_obj.CreateScreenshot_Task()
            wait_for_task(task)
        except vim.fault.FileFault as e:
            self.module.fail_json(msg="Failed to create screenshot due to errors when creating or accessing one or more"
                                      " files needed for this operation, %s" % to_native(e.msg))
        except vim.fault.InvalidState as e:
            self.module.fail_json(msg="Failed to create screenshot due to VM is not ready to respond to such requests,"
                                      " %s" % to_native(e.msg))
        except vmodl.RuntimeFault as e:
            self.module.fail_json(msg="Failed to create screenshot due to runtime fault, %s," % to_native(e.msg))
        except vim.fault.TaskInProgress as e:
            self.module.fail_json(msg="Failed to create screenshot due to VM is busy, %s" % to_native(e.msg))

        if task.info.state == 'error':
            return {'changed': self.change_detected, 'failed': True, 'msg': task.info.error.msg}
        else:
            download_file_size = None
            self.change_detected = True
            file_url = self.generate_http_access_url(task.info.result)
            if self.params.get('local_path'):
                if file_url:
                    download_file_size = self.download_screenshot_file(file_url=file_url,
                                                                       local_file_path=self.params['local_path'],
                                                                       file_name=task.info.result.split('/')[-1])
            screenshot_facts = self.get_screenshot_facts(task.info, file_url, download_file_size)
            return {'changed': self.change_detected, 'failed': False, 'screenshot_info': screenshot_facts}


def main():
    argument_spec = vmware_argument_spec()
    argument_spec.update(
        name=dict(type='str'),
        uuid=dict(type='str'),
        moid=dict(type='str'),
        folder=dict(type='str'),
        datacenter=dict(type='str'),
        esxi_hostname=dict(type='str'),
        cluster=dict(type='str'),
        local_path=dict(type='path'),
    )
    module = AnsibleModule(
        argument_spec=argument_spec,
        required_one_of=[
            ['name', 'uuid', 'moid']
        ]
    )
    pyv = PyVmomiHelper(module)
    vm = pyv.get_vm()
    if not vm:
        vm_id = (module.params.get('uuid') or module.params.get('name') or module.params.get('moid'))
        module.fail_json(msg='Unable to find the specified virtual machine : %s' % vm_id)

    result = pyv.take_vm_screenshot()
    if result['failed']:
        module.fail_json(**result)
    else:
        module.exit_json(**result)


if __name__ == '__main__':
    main()
