#
# 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.
#

from .base import ConfigValidator
from .azurevalidationhelpers import (
    vmss_status_succeeded_if_exists,
    vmss_cluster_has_appropriate_data_disk_count,
    vmss_exists,
)
from azure.mgmt.compute import ComputeManagementClient
from azure.common.client_factory import get_client_from_cli_profile


def validate_azure_configs(config, action):
    # get VM SKU resources for this location. we have to use
    # a specific API version to do this as this resource_skus
    # list operation is not allowed in any other API versions
    # which are available with the version of Azure SDK
    # that ships with Ansible for Azure
    config.client = get_client_from_cli_profile(
        ComputeManagementClient, api_version="2017-09-01"
    )
    config.vm_skus_for_location = list(
        filter(
            lambda s: s.resource_type == "virtualMachines"
            and config.location() in s.locations,
            config.client.resource_skus.list(),
        )
    )

    # switch to 2018-06-01 API which has support for other operations
    # including VMSS checks
    config.client = get_client_from_cli_profile(
        ComputeManagementClient, api_version="2018-06-01"
    )

    validations = (
        AZURE_VALIDATIONS["common"] + AZURE_VALIDATIONS[action]
        if action in AZURE_VALIDATIONS
        else []
    )
    return list(
        filter(
            lambda r: isinstance(r, str),
            map(lambda v: v(config, config.client), validations),
        )
    )


AZURE_VALIDATIONS = {
    "common": [
        # if VMSS instances are pending upgrade to latest version
        # block the execution of the setup phase.
        ConfigValidator(
            vmss_status_succeeded_if_exists,
            "VMSS must not exist or be in 'Succeeded' state",
        ),
        # Validate that the data disk configuration is appropriate
        # considering temp disk usage etc.
        ConfigValidator(vmss_cluster_has_appropriate_data_disk_count, None),
        ConfigValidator(lambda config, client: not config.use_multiple_vmss()),
        # the VM SKU specified is not a valid Azure VM SKU
        ConfigValidator(
            lambda config, client: config.vm_sku()
            in {s.name: s for s in config.vm_skus_for_location},
            "azure.vm_sku must be a valid VM SKU for the selected location",
        ),
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or all(
                [
                    vmss.get("sku")
                    in {s.name: s for s in config.vm_skus_for_location}
                    for vmss in config.azure_multiple_vmss_vars.get(
                        "vars_list", []
                    )
                ]
            ),
            "when use_multiple_vmss == True, any VMSS with sku "
            "must be a valid VM SKU for the selected location",
        ),
        # Cannot specify Spot (Low Priority) if VMSS SKU is / are not capable
        ConfigValidator(
            lambda config, client: config.getboolean(
                "azure", "use_multiple_vmss"
            )
            or not config.vmss_priority() == "Low"
            or config.vm_sku() in config.spot_capable_skus(),
            "azure.vm_sku must be an Azure Spot (low priority) capable VM SKU",
        ),
        ConfigValidator(
            lambda config, client: not config.getboolean(
                "azure", "use_multiple_vmss"
            )
            or all(
                [
                    vmss.get("sku") in config.spot_capable_skus()
                    if vmss.get("vmss_priority") == "Low"
                    else True
                    for vmss in config.azure_multiple_vmss_vars.get(
                        "vars_list", []
                    )
                ]
            ),
            "when use_multiple_vmss == True, any VMSS set to use Azure Spot "
            "(low priority) must use an Azure Spot-capable VM SKU",
        ),
        # data_disk_sku in
        # ['Standard_LRS', 'StandardSSD_LRS', Premium_LRS']
        ConfigValidator(
            lambda config, client: config.data_disk_sku()
            in ["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"],
            "data_disk_sku must be "
            "one of Standard_LRS, StandardSSD_LRS, or Premium_LRS",
        ),
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or all(
                [
                    vmss.get("data_disk_sku")
                    in ["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"]
                    for vmss in config.azure_multiple_vmss_vars.get(
                        "vars_list", []
                    )
                ]
            ),
            "when use_multiple_vmss == True, the data_disk_sku specified for "
            "the VMSS must be one of Standard_LRS, StandardSSD_LRS "
            "or Premium_LRS",
        ),
        # Cannot specify Premium managed disks if VMSS SKU is / are not capable
        ConfigValidator(
            lambda config, client: config.use_multiple_vmss()
            or not config.data_disk_sku() == "Premium_LRS"
            or config.vm_sku() in config.premiumio_capable_skus(),
            "azure.vm_sku must be Premium I/O capable VM SKU "
            "in order to use Premium Managed Disks",
        ),
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or all(
                [
                    vmss.get("sku") in config.premiumio_capable_skus()
                    if vmss.get("data_disk_sku") == "Premium_LRS"
                    else True
                    for vmss in config.azure_multiple_vmss_vars.get(
                        "vars_list", []
                    )
                ]
            ),
            "when use_multiple_vmss == True, any VMSS set to use Premium "
            "Managed Disks must use a Premium I/O capable VM SKU",
        ),
        # Data disk count specified cannot exceed MaxDataDisks for VM SKU
        ConfigValidator(
            lambda config, client: config.use_multiple_vmss()
            or config.data_disk_count()
            <= config.max_data_disks_for_skus().get(config.vm_sku(), 0),
            "Number of data disks specified exceeds allowed limit for VM SKU",
        ),
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or all(
                [
                    vmss.get("data_disk_count")
                    <= config.max_data_disks_for_skus().get(config.vm_sku(), 0)
                    for vmss in config.azure_multiple_vmss_vars.get(
                        "vars_list", []
                    )
                ]
            ),
            "when use_multiple_vmss == True, no VMSS can specify number of "
            "data disks exceeding the allowed limit for the respective VM SKU",
        ),
        # in the multiple VMSS case, a azure_multiple_vmss_vars.yml file
        # must be provided
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or hasattr(config, "azure_multiple_vmss_vars"),
            "in the multiple VMSS case, an azure_multiple_vmss_vars.yml"
            " file must be provided",
        ),
        # in the multiple VMSS case, each name suffix should be unique
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or len(config.azure_multiple_vmss_vars.get("vars_list", []))
            == len(
                set(
                    [
                        v.get("name_suffix")
                        for v in config.azure_multiple_vmss_vars.get(
                            "vars_list", []
                        )
                    ]
                )
            ),
            "in the multiple VMSS case, each name suffix of a VMSS"
            " must be unique",
        ),
        # ADLS Gen2 is only supported if Accumulo 2.x is used
        ConfigValidator(
            lambda config, client: not config.use_adlsg2()
            or config.version("accumulo").split(".")[0] == "2",
            "ADLS Gen2 support requires Accumulo 2.x",
        ),
    ],
    "launch": [
        # Fail when HDFS HA is NOT enabled and azure_multiple_vmss_vars.yml
        # specifies assignments for HA service roles
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or config.hdfs_ha()
            or all(
                (
                    "journalnode" not in current_vmss["roles"]
                    and "zkfc" not in current_vmss["roles"]
                )
                for current_vmss in config.azure_multiple_vmss_vars[
                    "vars_list"
                ]
            ),
            "HDFS HA is NOT enabled, but azure_multiple_vmss_vars.yml "
            "specifies assignments for HA service roles",
        ),
        # Fail when HDFS HA is enabled and azure_multiple_vmss_vars.yml
        # does NOT specify nodes with HA service roles
        ConfigValidator(
            lambda config, client: not config.use_multiple_vmss()
            or not config.hdfs_ha()
            or
            # TODO implement a count based check for the below,
            # do not just check existence of ZKFC and Journal Node roles
            (
                any(
                    ("journalnode" in current_vmss["roles"])
                    for current_vmss in config.azure_multiple_vmss_vars[
                        "vars_list"
                    ]
                )
                and any(
                    ("zkfc" in current_vmss["roles"])
                    for current_vmss in config.azure_multiple_vmss_vars[
                        "vars_list"
                    ]
                )
            ),
            "HDFS HA is enabled, but azure_multiple_vmss_vars.yml does NOT"
            " specify ZKFC and / or Journal Node service roles",
        ),
    ],
    "setup": [
        ConfigValidator(
            vmss_exists,
            "VMSS must exist, please run launch first before running setup",
        ),
    ],
    "wipe": [
        ConfigValidator(vmss_exists, "VMSS must exist to allow running wipe")
    ],
    "terminate": [
        ConfigValidator(
            vmss_exists, "VMSS must exist to allow running terminate"
        )
    ],
}
