import html
import re
from html import escape

from behave.formatter.ansi_escapes import colors, escapes

ESC_SEQ = r"\x1b["
TRANSLATION = (
    {colors["grey"]: '<span style="color: grey;">\n'}
    | {  # grey indicates a new step, and hence add a line break
        v: f'<span style="color: {k};">'
        for k, v in colors.items()
        if k != "grey"
    }
    | {
        "\n" + escapes["reset"] + "\n": "</span>\n",
        escapes["reset"]: "</span>",
        escapes["up"] + "\n": "",  # remove a line break with cursor up
        ESC_SEQ + "46": "",  # ignore DELETE (num keypad)
        ESC_SEQ + "48": "",  # ignore INSERT (num keypad)
        ESC_SEQ + "49": "",  # ignore END (num keypad)
        ESC_SEQ + "50": "",  # ignore DOWN ARROW (num keypad)
        ESC_SEQ + "51": "",  # ignore PAGE DOWN (num keypad)
        ESC_SEQ + "52": "",  # ignore LEFT ARROW (num keypad)
        ESC_SEQ + "54": "",  # ignore RIGHT ARROW (num keypad)
        ESC_SEQ + "55": "",  # ignore HOME (num keypad)
        ESC_SEQ + "56": "",  # ignore UP ARROW (num keypad)
        ESC_SEQ + "57": "",  # ignore PAGE UP (num keypad)
        ESC_SEQ + "1A": "",  # ignore move cursor up one line
    }
)
RE_TO_HTML = re.compile("|".join(map(re.escape, TRANSLATION)))

RE_TO_REMOVE = re.compile(
    r"\x1b\[(0;)?[0-9A-F]{1,2}[mA]:?"
)  # detect hex values, not just decimal digits


def remove_ansi(input: str) -> str:
    return RE_TO_REMOVE.sub("", input)


def parse_log_to_html(input: str) -> str:
    """
    Parse an ansi color log to html
    """
    html_start = "<!DOCTYPE html>\n<html>\n"
    html_end = "</html>\n"
    head = '<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n</head>\n'
    body_start = '<body style="color: white; background-color: #100c08;">'  # use dark bg since colors are from behave
    body_end = "</body>\n"
    result = f"{html_start}{head}{body_start}<pre>\n{RE_TO_HTML.sub(lambda match: TRANSLATION[match.group(0)], html.escape(input, quote=False))}\n</pre>{body_end}{html_end}"
    if ESC_SEQ in result:
        lines = "\n".join([x for x in result.split("\n") if ESC_SEQ in x])

        print(
            f"Detected unmapped ansi escape code!:\n{lines}"
        )  # use print instead of logger to avoid circular import

    return result


ANSI_CLASSES = {
    "0": [],  # reset
    "1": ["ansi-bold"],
    "30": ["ansi-black"],
    "31": ["ansi-red"],
    "32": ["ansi-green"],
    "33": ["ansi-yellow"],
    "34": ["ansi-blue"],
    "35": ["ansi-magenta"],
    "36": ["ansi-cyan"],
    "37": ["ansi-white"],
    "90": ["ansi-gray"],
}

ANSI_SGR_RE = re.compile(r"\x1b\[([0-9;]+)m")


def ansi_to_html(line: str) -> str:
    """
    Convert ANSI SGR codes to <span class="..."> HTML.
    """
    result = []
    open_spans = []

    pos = 0
    for match in ANSI_SGR_RE.finditer(line):
        chunk = line[pos : match.start()]
        if chunk:
            result.append(escape(chunk))

        codes = match.group(1).split(";")

        # reset
        if "0" in codes:
            while open_spans:
                result.append("</span>")
                open_spans.pop()
        else:
            classes = []
            for c in codes:
                classes.extend(ANSI_CLASSES.get(c, []))

            if classes:
                class_attr = " ".join(classes)
                result.append(f'<span class="{class_attr}">')
                open_spans.append("</span>")

        pos = match.end()

    # remainder
    remainder = line[pos:]
    if remainder:
        result.append(escape(remainder))

    # close any open spans
    while open_spans:
        result.append(open_spans.pop())

    return "".join(result)
