# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
from typing import Any
import re
import argparse

SWAGGER_PACKAGE_MODE = ["mgmtplane", "dataplane"]  # for backward compatibility
TYPESPEC_PACKAGE_MODE = ["azure-mgmt", "azure-dataplane", "generic"]
VALID_PACKAGE_MODE = SWAGGER_PACKAGE_MODE + TYPESPEC_PACKAGE_MODE

CODE_BLOCK_MARKER = ".. code-block::"


def description_ends_with_code_block(description: str) -> bool:
    """Return True when the description's trailing content is an RST ``.. code-block::``.

    True when the last ``.. code-block::`` directive (starting its own line, so inline
    mentions are ignored) is followed only by blank or indented lines, i.e. the literal
    block runs to the end of the description.
    """
    lines = description.rstrip().splitlines()
    directives = [i for i, line in enumerate(lines) if line.lstrip().startswith(CODE_BLOCK_MARKER)]
    if not directives:
        return False
    return all(not line.strip() or line.startswith((" ", "\t")) for line in lines[directives[-1] + 1 :])


def update_enum_value(name: str, value: Any, description: str, enum_type: dict[str, Any]) -> dict[str, Any]:
    return {
        "name": name,
        "type": "enumvalue",
        "value": value,
        "description": description,
        "enumType": enum_type,
        "valueType": enum_type["valueType"],
    }


def to_snake_case(name: str) -> str:
    def replace_upper_characters(m) -> str:
        match_str = m.group().lower()
        if m.start() > 0 and name[m.start() - 1] == "_":
            # we are good if a '_' already exists
            return match_str
        # the first letter should not have _
        prefix = "_" if m.start() > 0 else ""

        # we will add an extra _ if there are multiple upper case chars together
        next_non_upper_case_char_location = m.start() + len(match_str)
        if (
            len(match_str) > 2
            and len(name) - next_non_upper_case_char_location > 1
            and name[next_non_upper_case_char_location].isalpha()
        ):
            return prefix + match_str[: len(match_str) - 1] + "_" + match_str[len(match_str) - 1]

        return prefix + match_str

    result = re.sub("[A-Z]+", replace_upper_characters, name)
    return result.replace(" ", "_").replace("__", "_").replace("-", "")


def parse_args(
    need_tsp_file: bool = True,
) -> tuple[argparse.Namespace, dict[str, Any]]:
    parser = argparse.ArgumentParser(
        description="Run mypy against target folder. Add a local custom plugin to the path prior to execution. "
    )
    parser.add_argument(
        "--output-folder",
        dest="output_folder",
        help="Output folder for generated SDK",
        required=True,
    )
    parser.add_argument(
        "--tsp-file",
        dest="tsp_file",
        help="Serialized tsp file",
        required=need_tsp_file,
    )
    parser.add_argument(
        "--debug",
        dest="debug",
        help="Debug mode",
        required=False,
        action="store",
    )
    args, unknown_args = parser.parse_known_args()

    def _get_value(value: Any) -> Any:
        if value == "true":
            return True
        if value == "false":
            return False
        try:
            return int(value)
        except ValueError:
            pass
        return value

    unknown_args_ret = {
        ua.strip("--").split("=", maxsplit=1)[0]: _get_value(ua.strip("--").split("=", maxsplit=1)[1])
        for ua in unknown_args
    }
    return args, unknown_args_ret


def get_body_type_for_description(body_parameter: dict[str, Any]) -> str:
    if body_parameter["type"]["type"] in ("binary", "bytes"):
        return "binary"
    if body_parameter["type"]["type"] == "string":
        return "string"
    return "JSON"


# used if we want to get a string / binary type etc
KNOWN_TYPES: dict[str, dict[str, Any]] = {
    "string": {"type": "string"},
    "binary": {"type": "binary"},
    "anydict": {"type": "dict", "elementType": {"type": "any"}},
    "any-object": {"type": "any-object"},
}

JSON_REGEXP = re.compile(r"^(application|text)/(.+\+)?json$")
XML_REGEXP = re.compile(r"^(application|text)/(.+\+)?xml$")


def build_policies(
    is_arm: bool,
    async_mode: bool,
    *,
    is_azure_flavor: bool = False,
    tracing: bool = True,
) -> list[str]:
    if is_azure_flavor:
        # for Azure
        async_prefix = "Async" if async_mode else ""
        policies = [
            "policies.RequestIdPolicy(**kwargs)",
            "self._config.headers_policy",
            "self._config.user_agent_policy",
            "self._config.proxy_policy",
            "policies.ContentDecodePolicy(**kwargs)",
            (f"{async_prefix}ARMAutoResourceProviderRegistrationPolicy()" if is_arm else None),
            "self._config.redirect_policy",
            "self._config.retry_policy",
            "self._config.authentication_policy",
            "self._config.custom_hook_policy",
            "self._config.logging_policy",
            "policies.DistributedTracingPolicy(**kwargs)" if tracing else None,
            "policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None",
            "self._config.http_logging_policy",
        ]
    else:
        # for non-Azure
        policies = [
            "self._config.headers_policy",
            "self._config.user_agent_policy",
            "self._config.proxy_policy",
            "policies.ContentDecodePolicy(**kwargs)",
            "self._config.retry_policy",
            "self._config.authentication_policy",
            "self._config.logging_policy",
        ]
    return [p for p in policies if p]


def extract_original_name(name: str) -> str:
    return name[1 : -len("_initial")]


def json_serializable(content_type: str) -> bool:
    return bool(JSON_REGEXP.match(content_type.split(";")[0].strip().lower()))


def xml_serializable(content_type: str) -> bool:
    return bool(XML_REGEXP.match(content_type.split(";")[0].strip().lower()))


NAME_LENGTH_LIMIT = 40
