#  Copyright (c) 2015-2018 Cisco Systems, Inc.
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to
#  deal in the Software without restriction, including without limitation the
#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
"""Kubevirt Driver Module."""

from __future__ import absolute_import

from molecule import logger, util
from molecule.api import Driver

log = logger.get_logger(__name__)


class KubeVirt(Driver):
    """
    Kubevirt Driver Class.
    ! Very alpha version - All configuration fields and behaviours may be subject to breaking changes !

    The class responsible for managing `Docker`_ containers.  `Docker`_ is
    the default driver used in Molecule.

    Molecule leverages Ansible's `kubevirt_vm`_ module, by mapping
    variables from ``molecule.yml`` into ``create.yml`` and ``destroy.yml``.

    .. _`kubevirt_vm`: https://docs.ansible.com/ansible/latest/collections/community/general/kubevirt_vm_module.html
    .. _`k8s`: https://docs.ansible.com/ansible/latest/collections/community/kubernetes/k8s_module.html

    .. code-block:: yaml

        driver:
          name: kubevirt
        platforms:
          - name: instance
            namespace: default
            wait_timeout: 300
            memory: 2Gi
            cpu_cores: (omit)
            machine_type: q35
            cpu_model: (omit)
            autoattachGraphicsDevice: false
            memory_request: memory
            cpu_request: (omit)
            memory_limit: memory
            cpu_limit: (omit)
            image: quay.io/kubevirt/fedora-cloud-container-disk-demo:latest
            annotations: {}
            ssh_service:
                type: ClusterIP
                clusterIP: {}
                nodePort: {}
                nodePort_host: localhost
            volumes: []
            networks: []
            domain: {} # domain is merged with default
            user_data: {} # user data cloud-config is merged with default
    Image MUST be accessible on Kubernetes workers running Kubevirt. This driver
    provides no service for building images. Solutions using CDI may be found in
    later version .

    Minimal authorizations are required for the molecule runner if running from Kubernetes, via ServiceAccount :
    .. code-block::yaml
        roleRef:
          apiGroup: rbac.authorization.k8s.io
          kind: ClusterRole
          name: kubevirt.io:edit

    .. note:: This driver does not require specific container privileges.

    .. note:: Default ssh access is curently set via a ClusterIp Service and points to
    {{ platform.name }}.{{ platform.namespace }}.svc
    . This may change in futur releases.

    .. code-block:: bash

        $ python3 -m pip install molecule[kubevirt]

    Provide a list of files Molecule will preserve, relative to the scenario
    ephemeral directory, after any ``destroy`` subcommand execution.

    .. code-block:: yaml

        driver:
          name: kubevirt
          safe_files:
            - foo

    .. _`Kubevirt`: https://kubevirt.io/
    """  # noqa

    def __init__(self, config=None):
        """Construct Kubevirt."""
        super(KubeVirt, self).__init__(config)
        self._name = "kubevirt"

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    # IMPORTANT : use NodePort for ssh ok (or port-forward ? => option)
    # https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2009.qcow2
    @property
    def default_safe_files(self):
        return (
            []
        )  # FIXME : should ssh-keys generated by create.yml playbook be set as safe_file ?

    @property
    def login_cmd_template(self):
        connection_options = " ".join(self.ssh_connection_options)

        return (
            "ssh {{address}} "
            "-l {{user}} "
            "-p {{port}} "
            "-i {{identity_file}} "
            "{}"
        ).format(connection_options)

    @property
    def default_ssh_connection_options(self):
        return self._get_ssh_connection_options()

    def _get_instance_config(self, instance_name):
        instance_config_dict = util.safe_load_file(self._config.driver.instance_config)

        return next(
            item for item in instance_config_dict if item["instance"] == instance_name
        )

    def login_options(self, instance_name):
        d = {"instance": instance_name}

        return util.merge_dicts(d, self._get_instance_config(instance_name))

    def ansible_connection_options(self, instance_name):
        try:
            d = self._get_instance_config(instance_name)

            return {
                "ansible_user": d["user"],
                "ansible_host": d["address"],
                "ansible_port": d["port"],
                "ansible_private_key_file": d["identity_file"],
                "connection": "ssh",
                "ansible_ssh_common_args": " ".join(self.ssh_connection_options),
            }
        except StopIteration:
            return {}
        except IOError:
            # Instance has yet to be provisioned , therefore the
            # instance_config is not on disk.
            return {}

    def sanity_checks(self):
        # FIXME : What kind of sanity should be done ?
        # => testing connection to kubernetes ? testing kubevirt is OK ?
        """Implement some (?) driver sanity checks."""
        log.info("Sanity checks: '{}'".format(self._name))

    def reset(self):
        # FIXME : Kubevirt VMs are already destroyed if failed via destroy.yml
        # => what "reset could be implemented" ?
        log.info("Stopping (fake)")
