import re
from functools import partial

# pylint: disable=protected-access

palete = {
    "black": "\033[30m",
    "red": "\033[31m",
    "green": "\033[32m",
    "orange": "\033[33m",
    "blue": "\033[34m",
    "purple": "\033[35m",
    "cyan": "\033[36m",
    "lightgrey": "\033[37m",
    "darkgrey": "\033[90m",
    "lightred": "\033[91m",
    "lightgreen": "\033[92m",
    "yellow": "\033[93m",
    "lightblue": "\033[94m",
    "pink": "\033[95m",
    "lightcyan": "\033[96m",
    "end": "\033[0m",
    "bold": "\033[1m",
    "underline": "\033[4m",
}

separator1 = "=" * 70
separator2 = "-" * 70

# apply is builtin but is deprecated since 2.3 => no problem to redefine it here
def apply(key_list, text):
    return "".join([palete[key] for key in key_list]) + text + palete["end"]


lightgrey = partial(apply, ["lightgrey"])
bold = partial(apply, ["bold"])
blue = partial(apply, ["blue", "bold"])
red = partial(apply, ["red", "bold"])
green = partial(apply, ["green", "bold"])


def format_module_name(name):
    prefix = ""
    part_list = name.split("_")

    if part_list[0].startswith("test"):
        prefix = "test"
        part_list[0] = part_list[0][len("test") :]

    return prefix + "_".join([bold(part) for part in part_list])


def format_module(test):
    parts = test.__class__.__module__.split(".")
    return lightgrey(".").join(parts[:-1] + [format_module_name(parts[-1])])


def format_test_method_name(test):
    if not hasattr(test, "_testMethodName"):
        return test.id()

    parts = test._testMethodName.split("_")
    if parts[0].startswith("test"):
        parts[0] = lightgrey("test") + parts[0][len("test") :]
    return lightgrey("_").join(parts)


def format_error_overview(errors, failures, slash_last):
    return (
        [
            red(
                "for running failed tests only (errors are first then "
                "failures):"
            ),
            "",
        ]
        + [
            lightgrey(err)
            for err in slash_errors(
                [format_test_name(test) for test, _ in errors + failures],
                slash_last,
            )
        ]
        + [""]
    )


def slash_errors(error_list, slash_last=True):
    if not slash_last:
        return slash_errors(error_list[:-1]) + [error_list[-1]]
    return ["{0} \\".format(err) for err in error_list]


def format_test_name(test):
    if (
        test.__class__.__module__ == "subunit"
        and test.__class__.__name__ == "RemotedTestCase"
    ):
        return format_test_method_name(test)
    return (
        format_module(test)
        + "."
        + test.__class__.__name__
        + "."
        + format_test_method_name(test)
    )


def get_description(test, descriptions):
    doc_first_line = test.shortDescription()
    if descriptions and doc_first_line:
        return "\n".join((str(test), doc_first_line))
    module_parts = test.__class__.__module__.split(".")
    module = module_parts[-1]
    package = ".".join(module_parts[:-1]) + "." if module_parts else ""

    return (
        test._testMethodName
        + " "
        + lightgrey("(")
        + lightgrey(package)
        + bold(module)
        + "."
        + test.__class__.__name__
        + lightgrey(")")
    )


def format_error_list(flavour, errors, descriptions, traceback_highlight):
    line_list = []
    for test, err in errors:
        line_list.extend(
            [
                lightgrey(separator1),
                "%s: %s" % (red(flavour), get_description(test, descriptions)),
                lightgrey(separator2),
                "%s" % format_traceback(err) if traceback_highlight else err,
            ]
        )
    return line_list


def format_traceback(err):
    formated_err = []
    path_regex = re.compile(
        r'^  File "(?P<path>[^"]+)", line (?P<line>\d+), in (?P<name>.*)$'
    )
    was_prev_path = False
    for line in err.splitlines():
        if line == "Traceback (most recent call last):":
            formated_err.append(lightgrey(line))
            was_prev_path = False
            continue

        match = path_regex.match(line)
        if match:
            path = match.group("path").split("/")
            formated_err.append(
                lightgrey('  File "')
                + lightgrey("/").join(path[:-1] + [bold(path[-1])])
                + lightgrey('", line ')
                + bold(match.group("line"))
                + lightgrey(", in ")
                + bold(match.group("name"))
            )
            was_prev_path = True
        elif was_prev_path:
            formated_err.append(bold(line))
            was_prev_path = False
        else:
            formated_err.append(line)
            was_prev_path = False
    return "\n".join(formated_err) + "\n"


def format_skips(skip_map):
    return (
        [blue("Some tests have been skipped:")]
        + [
            lightgrey("{0} ({1}x)".format(reason, len(test_list)))
            for reason, test_list in skip_map.items()
        ]
        + [""]
    )
