# Copyright NTESS. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: MIT

import typing

from schema import And
from schema import Optional
from schema import Or
from schema import Regex
from schema import Schema
from schema import SchemaError
from schema import Use

from ..util.time import time_in_seconds


def list_of_str(arg: typing.Any) -> bool:
    return isinstance(arg, list) and all([isinstance(_, str) for _ in arg])


def list_of_int(arg: typing.Any) -> bool:
    return isinstance(arg, list) and all([isinstance(_, int) for _ in arg])


def vardict(arg: typing.Any) -> bool:
    if arg is None:
        return True
    if not isinstance(arg, dict):
        return False
    for key, value in arg.items():
        if not isinstance(key, str):
            return False
        if not isinstance(value, str):
            return False
    return True


def log_level_name(arg: typing.Any) -> str:
    logging_levels = ("TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
    if isinstance(arg, str):
        level_name = arg.upper()
        if level_name not in logging_levels:
            s = ", ".join(logging_levels)
            raise SchemaError(
                f"Wrong log level {level_name!r}, choose from {', '.join(logging_levels)}"
            )
        return level_name
    raise SchemaError(f"Wrong log level {arg!r}, choose from {', '.join(logging_levels)}")


def boolean(arg: typing.Any) -> bool:
    if isinstance(arg, str):
        return arg.lower() not in ("0", "off", "false", "no")
    return bool(arg)


positive_int = And(int, lambda x: x > 0)  # type: ignore
nonnegative_int = And(int, lambda x: x >= 0)  # type: ignore
optional_str = Or(str, None)  # type: ignore

any_schema = Schema({}, ignore_extra_keys=True)

environment_schema = Schema(
    {
        Optional("set"): vardict,
        Optional("unset"): list_of_str,
        Optional("prepend-path"): vardict,
        Optional("append-path"): vardict,
    }
)
workspace_schema = Schema({Optional("view", default="TestResults"): Or(bool, str, None)})
run_schema = Schema(
    {
        Optional("default_tag", default=":all:"): str,
        Optional("timeout"): {Optional(str): Use(time_in_seconds)},
    }
)

config_schema = Schema(
    {
        Optional("debug"): Use(boolean),
        Optional("log_level"): Use(log_level_name),
        Optional("workspace", default={"view": "TestResults"}): workspace_schema,
        Optional("plugins"): list_of_str,
        Optional("environment"): environment_schema,
        Optional("scratch"): any_schema,
        Optional("run"): run_schema,
    },
    ignore_extra_keys=True,
)
testpaths_schema = Schema({"testpaths": [{"root": str, "paths": list_of_str}]})


class EnvarSchema(Schema):
    def validate(self, data, is_root_eval=True):
        data = super().validate(data, is_root_eval=False)
        if is_root_eval:
            validated = {}
            for key, val in data.items():
                name = key[7:].lower()
                if name.startswith(("timeout_",)):
                    root, _, leaf = name.partition("_")
                    validated.setdefault(root, {})[leaf] = val
                elif name.endswith("_polling_frequency"):
                    leaf, _, root = name.partition("_")
                    validated.setdefault(root, {})[leaf] = val
                else:
                    validated[name] = val
            return validated
        return data


environment_variable_schema = EnvarSchema(
    {
        Optional("CANARY_DEBUG"): Use(boolean),
        Optional("CANARY_LOG_LEVEL"): Use(log_level_name),
        Optional("CANARY_PLUGINS"): Use(lambda x: [_.strip() for _ in x.split(",") if _.split()]),
        Optional(Regex("CANARY_TIMEOUT_w+")): Use(time_in_seconds),
        Optional("CANARY_TESTCASE_POLLING_FREQUENCY"): Use(time_in_seconds),
    },
    ignore_extra_keys=True,
)
