#!/usr/bin/env python3
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import json
import subprocess
from os import path
from azure.common.client_factory import get_client_from_cli_profile
from azure.mgmt.compute import ComputeManagementClient
from .existing import ExistingCluster


# For Muchos deployments on Microsoft Azure, we use Virtual Machine Scale Sets
# VMSS is the most efficient way to provision large numbers of VMs in Azure
class VmssCluster(ExistingCluster):
    def __init__(self, config):
        ExistingCluster.__init__(self, config)

    def launch(self):
        config = self.config
        azure_config = config.ansible_host_vars()
        azure_config["vmss_name"] = config.cluster_name

        retcode = subprocess.call(
            [
                "ansible-playbook",
                path.join(config.deploy_path, "ansible/azure.yml"),
                "--extra-vars",
                json.dumps(azure_config),
            ]
        )
        if retcode != 0:
            exit(
                "ERROR - Command failed with return code of {0}".format(
                    retcode
                )
            )

    def status(self):
        config = self.config
        azure_vars_dict = dict(config.items("azure"))
        compute_client = get_client_from_cli_profile(ComputeManagementClient)
        vmss_status = compute_client.virtual_machine_scale_sets.get(
            azure_vars_dict["resource_group"], self.config.cluster_name
        )
        print(
            "name:",
            vmss_status.name,
            "\nprovisioning_state:",
            vmss_status.provisioning_state,
        )

    def terminate(self):
        config = self.config
        azure_config = dict(config.items("azure"))
        azure_config["vmss_name"] = config.cluster_name
        azure_config["deploy_path"] = config.deploy_path
        azure_config = {
            k: VmssCluster._parse_config_value(v)
            for k, v in azure_config.items()
        }
        print(
            "All of the Muchos resources provisioned in resource group '{0}'"
            " will be deleted!".format(azure_config["resource_group"])
        )

        response = input("Do you want to continue? (y/n) ")
        if response == "y":
            subprocess.call(
                [
                    "ansible-playbook",
                    path.join(
                        config.deploy_path,
                        "ansible/azure_terminate.yml",
                    ),
                    "--extra-vars",
                    json.dumps(azure_config),
                ]
            )
        else:
            print("Aborted termination")

    def wipe(self):
        super().wipe()
        # Wipe ADLS Gen2 storage accounts if implemented
        config = self.config
        azure_config = dict(config.items("azure"))
        azure_config["vmss_name"] = config.cluster_name
        azure_config["cluster_type"] = config.get("general", "cluster_type")
        azure_config["deploy_path"] = config.deploy_path
        azure_config = {
            k: VmssCluster._parse_config_value(v)
            for k, v in azure_config.items()
        }
        retcode = subprocess.call(
            [
                "ansible-playbook",
                path.join(
                    config.deploy_path,
                    "ansible/azure_wipe.yml",
                ),
                "--extra-vars",
                json.dumps(azure_config),
            ]
        )
        if retcode != 0:
            exit(
                "ERROR - Command failed with return code of {0}".format(
                    retcode
                )
            )

    def _parse_config_value(v):  # noqa
        if v.isdigit():
            return int(v)
        if v.lower() in ("true", "yes"):
            return True
        if v.lower() in ("false", "no"):
            return False
        return v

    # For Azure clusters this method creates Ansible group variables which
    # allow overriding the "global" host or play variables with group specific
    # variables. Because Ansible group variables override host variables this
    # is a very powerful feature to support per-group specialization of
    # configuration. Currently this is used to define the following:
    #
    # 1. Variables for different perf profiles for different groups of hosts
    #    This capability allows specifying different settings for clusters
    #    which have heterogenous hardware - RAM especially
    #
    # 2. Different mount roots for different sets of hosts, with a fallback to
    #    using the global mount_root defined in the Ansible hosts file
    #
    # 3. Different worker_data_dirs and default_data_dirs for specific groups
    #    of hosts.
    #
    # 4. Different Azure disk path and disk name pattern for specific groups
    #    of hosts.
    def add_specialized_configs(self, hosts_file):
        if self.config.use_multiple_vmss():
            vmss_hosts = open(
                path.join(
                    self.config.deploy_path, "conf/azure_vmss_to_hosts.conf"
                ),
                "r",
            )
            print("\n", file=hosts_file)
            for line in vmss_hosts:
                print(line.rstrip("\n"), file=hosts_file)

            for curr_vmss in self.config.azure_multiple_vmss_vars["vars_list"]:
                vmss_group_name = (
                    self.config.cluster_name + "-" + curr_vmss["name_suffix"]
                )
                profile = curr_vmss["perf_profile"]

                with open(
                    path.join(
                        self.config.deploy_path,
                        "ansible/group_vars/"
                        + vmss_group_name.replace("-", "_"),
                    ),
                    "w",
                ) as vmss_file:
                    for (name, value) in self.config.items(profile):
                        print("{0}: {1}".format(name, value), file=vmss_file)

                    # use VMSS-specific mount root if one is defined or
                    # the global mount root if there is no VMSS-specific value
                    curr_mount_root = curr_vmss.get(
                        "mount_root", self.config.mount_root()
                    )

                    # write the mount root out to the per-VMSS group vars
                    print(
                        "{0}: {1}".format("mount_root", curr_mount_root),
                        file=vmss_file,
                    )

                    # also include per-VMSS worker_data_dirs
                    curr_worker_dirs = self.config.data_dirs_internal(
                        "worker",
                        curr_vmss["data_disk_count"],
                        curr_mount_root,
                        curr_vmss["sku"],
                    )

                    print(
                        "{0}: {1}".format(
                            "worker_data_dirs",
                            curr_worker_dirs,
                        ),
                        file=vmss_file,
                    )

                    # per-VMSS default_data_dirs
                    curr_default_dirs = self.config.data_dirs_internal(
                        "default",
                        curr_vmss["data_disk_count"],
                        curr_mount_root,
                        curr_vmss["sku"],
                    )

                    print(
                        "{0}: {1}".format(
                            "default_data_dirs",
                            curr_default_dirs,
                        ),
                        file=vmss_file,
                    )

                    # also write out per-VMSS disk path and pattern
                    # using the global value from muchos.props as default
                    # if the VMSS does not define a custom value
                    print(
                        "{0}: {1}".format(
                            "azure_disk_device_path",
                            curr_vmss.get(
                                "azure_disk_device_path",
                                self.config.azure_disk_device_path(),
                            ),
                        ),
                        file=vmss_file,
                    )

                    print(
                        "{0}: {1}".format(
                            "azure_disk_device_pattern",
                            curr_vmss.get(
                                "azure_disk_device_pattern",
                                self.config.azure_disk_device_pattern(),
                            ),
                        ),
                        file=vmss_file,
                    )

                    # these nested loops are a tight (if slightly less
                    # readable way) of creating the various directory ordinals
                    for dirtype in ["default", "worker"]:
                        for ordinal in range(3):
                            print(
                                "{0}: {1}".format(
                                    "{0}dir_ordinal{1}".format(
                                        dirtype, ordinal
                                    ),
                                    0
                                    if len(
                                        curr_default_dirs
                                        if dirtype == "default"
                                        else curr_worker_dirs
                                    )
                                    < ordinal + 1
                                    else ordinal,
                                ),
                                file=vmss_file,
                            )
