#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: expandtab:tabstop=4:shiftwidth=4
# pylint: disable=missing-docstring,invalid-name
#

import random
import tempfile
import shutil
import os.path

# pylint: disable=redefined-builtin,wildcard-import,unused-wildcard-import
from ansible.module_utils.basic import *  # noqa: F403


DOCUMENTATION = '''
---
module: openshift_container_binary_sync
short_description: Copies OpenShift binaries out of the given image tag to host system.
'''


class BinarySyncError(Exception):
    def __init__(self, msg):
        super(BinarySyncError, self).__init__(msg)
        self.msg = msg


# pylint: disable=too-few-public-methods
class BinarySyncer(object):
    """
    Syncs the openshift, oc, oadm, and kubectl binaries/symlinks out of
    a container onto the host system.
    """

    def __init__(self, module, image, tag):
        self.module = module
        self.changed = False
        self.output = []
        self.bin_dir = '/usr/local/bin'
        self.image = image
        self.tag = tag
        self.temp_dir = None  # TBD

    def sync(self):
        container_name = "openshift-cli-%s" % random.randint(1, 100000)
        rc, stdout, stderr = self.module.run_command(['docker', 'create', '--name',
                                                      container_name, '%s:%s' % (self.image, self.tag)])
        if rc:
            raise BinarySyncError("Error creating temporary docker container. stdout=%s, stderr=%s" %
                                  (stdout, stderr))
        self.output.append(stdout)
        try:
            self.temp_dir = tempfile.mkdtemp()
            self.output.append("Using temp dir: %s" % self.temp_dir)

            rc, stdout, stderr = self.module.run_command(['docker', 'cp', "%s:/usr/bin/openshift" % container_name,
                                                          self.temp_dir])
            if rc:
                raise BinarySyncError("Error copying file from docker container: stdout=%s, stderr=%s" %
                                      (stdout, stderr))

            rc, stdout, stderr = self.module.run_command(['docker', 'cp', "%s:/usr/bin/oc" % container_name,
                                                          self.temp_dir])
            if rc:
                raise BinarySyncError("Error copying file from docker container: stdout=%s, stderr=%s" %
                                      (stdout, stderr))

            self._sync_binary('openshift')

            # In older versions, oc was a symlink to openshift:
            if os.path.islink(os.path.join(self.temp_dir, 'oc')):
                self._sync_symlink('oc', 'openshift')
            else:
                self._sync_binary('oc')

            # Ensure correct symlinks created:
            self._sync_symlink('kubectl', 'openshift')
            self._sync_symlink('oadm', 'openshift')
        finally:
            shutil.rmtree(self.temp_dir)
            self.module.run_command(['docker', 'rm', container_name])

    def _sync_symlink(self, binary_name, link_to):
        """ Ensure the given binary name exists and links to the expected binary. """

        # The symlink we are creating:
        link_path = os.path.join(self.bin_dir, binary_name)

        # The expected file we should be linking to:
        link_dest = os.path.join(self.bin_dir, link_to)

        if not os.path.exists(link_path) or \
                not os.path.islink(link_path) or \
                os.path.realpath(link_path) != os.path.realpath(link_dest):
            if os.path.exists(link_path):
                os.remove(link_path)
            os.symlink(link_to, os.path.join(self.bin_dir, binary_name))
            self.output.append("Symlinked %s to %s." % (link_path, link_dest))
            self.changed = True

    def _sync_binary(self, binary_name):
        src_path = os.path.join(self.temp_dir, binary_name)
        dest_path = os.path.join(self.bin_dir, binary_name)
        incoming_checksum = self.module.run_command(['sha256sum', src_path])[1]
        if not os.path.exists(dest_path) or self.module.run_command(['sha256sum', dest_path])[1] != incoming_checksum:
            shutil.move(src_path, dest_path)
            self.output.append("Moved %s to %s." % (src_path, dest_path))
            self.changed = True


def main():
    module = AnsibleModule(  # noqa: F405
        argument_spec=dict(
            image=dict(required=True),
            tag=dict(required=True),
        ),
        supports_check_mode=True
    )

    image = module.params['image']
    tag = module.params['tag']

    binary_syncer = BinarySyncer(module, image, tag)

    try:
        binary_syncer.sync()
    except BinarySyncError as ex:
        module.fail_json(msg=ex.msg)

    return module.exit_json(changed=binary_syncer.changed,
                            output=binary_syncer.output)


if __name__ == '__main__':
    main()
