# Copyright 2004-2023 Bright Computing Holding BV
#
# Licensed 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 __future__ import absolute_import

import logging
import os
import secrets
import string
import textwrap
from datetime import datetime

import six
from six.moves import range

import clusterondemand.tracing as tracing
from clusterondemand import const
from clusterondemand.clusternameprefix import ensure_cod_prefix
from clusterondemand.exceptions import CODException
from clusterondemand.tags import format_cluster_tags, format_packagegroups_tags
from clusterondemandconfig import config

log = logging.getLogger("cluster-on-demand")


def generate_random_cluster_password(length):
    assert length > 0
    assert length < 256

    letters = string.ascii_letters if six.PY3 else string.letters
    chars = letters + string.digits
    password = "".join((secrets.choice(chars)) for x in range(length))
    return password


def enable_cmd_debug_commands(subsystems="*", base_paths=None):
    """Return list with the proper commmands to enable debug on logging.cmd.conf"""
    LOG_CONF = "cm/local/apps/cmd/etc/logging.cmd.conf"

    if not base_paths:
        base_paths = ["/", "/cm/images/default-image/"]

    # We want to replace "debug: " for "debug: subsystems", but not on the commented lines
    # that's why "/\\#/"
    return [
        "sed -i '/\\#/! s/debug:.*/debug: {subsystems}/' {path}".format(
            subsystems=subsystems,
            path=os.path.join(base_path, LOG_CONF),
        )
        for base_path in base_paths
    ]


def set_cmd_advanced_config_commands(parameters, base_paths=None):
    """
    Return list with the proper commands to configure specified AdvancedConfig parameters on cmd.conf

    :param parameters: a list of parameters in the ["param1=value1", "param2=value2", ...] format
    :param base_paths: a list of filesystem prefixes
    """
    CMD_CONF = "cm/local/apps/cmd/etc/cmd.conf"

    assert parameters and all(isinstance(p, str) for p in parameters)

    if not base_paths:
        base_paths = ["/", "/cm/images/default-image/"]

    return [
        "! grep -P '^ *AdvancedConfig *=' \"{path}\""  # Make sure it wasn't set before
        " && echo 'AdvancedConfig = {{ {params} }}' >> \"{path}\"".format(
            params=", ".join(f'"{p}"' for p in parameters),
            path=os.path.join(base_path, CMD_CONF),
        )
        for base_path in base_paths
    ]


def cluster_name_generator(head_node_image):
    version = head_node_image.version
    if version == "trunk":
        version = version.replace("trunk", "t")
    else:
        version = "b" + version.replace(".", "")

    distro = head_node_image.distro
    distro = distro.lower().replace("centos", "c")
    distro = distro.lower().replace("sles", "s")
    distro = distro.lower().replace("ubuntu", "u")
    distro = distro.lower().replace("rocky", "r")
    distro = distro.lower().replace("alma", "a")

    now = datetime.now()

    base_name = ensure_cod_prefix(f"{version}-{distro}-{now:%m-%d}")
    yield base_name

    index = 0
    while True:
        index = index + 1
        yield f"{base_name}-{index}"


def make_cluster_name(head_node_image, cluster_exists_func):
    """
    Creates a unique non-existent depending on user input
    """
    if config["name"] is None:
        for name in cluster_name_generator(head_node_image):
            if not cluster_exists_func(name):
                return name
    else:
        name = ensure_cod_prefix(config["name"])
        if cluster_exists_func(name):
            raise CODException(textwrap.dedent(
                "A cluster with the name '%s' already exists. Remember that "
                "you can skip specifying --name, which will result in an autogenerated "
                "unique name for your cluster. " % name
            ))
        return name

    # Unreachable. Gotta make the linter happy
    return None


def generate_uuid_tag(head_node_image):
    return format_cluster_tags({const.COD_HEAD_IMAGE_UUID: head_node_image.uuid})[0]


def generate_cluster_tags(head_node_image, only_image_tags=False):
    tags = [const.COD_TAG]
    tags += format_cluster_tags({
        const.COD_TOOL_VERSION_TAG: const.COD_TOOL_VERSION,
        const.COD_DISTRO_TAG: head_node_image.distro,
        const.COD_VERSION_TAG: head_node_image.version,
        const.COD_CMDREV_TAG: head_node_image.cmd_revision,
        const.COD_HEAD_IMAGE_CREATED_AT: head_node_image.created_at,
        const.COD_HEAD_IMAGE_ID: head_node_image.id,
        const.COD_HEAD_IMAGE_NAME: head_node_image.name,
        const.COD_HEAD_IMAGE_REV: head_node_image.revision,
    })
    tags.append(generate_uuid_tag(head_node_image))

    # On vmware we cannot create any new tags on cluster creation
    # So we leave this separate
    if not only_image_tags:
        tags += format_cluster_tags({
            const.COD_TRACE_ID: tracing.get_trace_id(),
        })

    tags += format_packagegroups_tags(head_node_image.package_groups)
    return tags


def detect_duplicate_tags(first, second):
    for tag1 in first:
        if "=" in tag1:
            match = tag1.split("=")[0]
            for tag2 in second:
                if tag2.startswith(match):
                    return match
        else:
            if tag1 in second:
                return tag1
    return None
