import base64
import re
import sys
import urwid
from collections import OrderedDict
from functools import reduce
from html.parser import HTMLParser
from typing import List

HASHTAG_PATTERN = re.compile(r'(?<!\w)(#\w+)\b')


def highlight_keys(text, high_attr, low_attr=""):
    """
    Takes a string and adds high_attr attribute to parts in square brackets,
    and optionally low_attr attribute to parts outside square brackets.

    The result can be rendered using a urwid.Text widget.

    For example:

    >>> highlight_keys("[P]rint [V]iew", "blue")
    >>> [('blue', 'P'), 'rint ', ('blue', 'V'), 'iew']
    """
    def _gen():
        highlighted = False
        for part in re.split("\\[|\\]", text):
            if part:
                if highlighted:
                    yield (high_attr, part) if high_attr else part
                else:
                    yield (low_attr, part) if low_attr else part
            highlighted = not highlighted
    return list(_gen())


def highlight_hashtags(line):
    hline = []

    for p in re.split(HASHTAG_PATTERN, line):
        if p.startswith("#"):
            hline.append(("hashtag", p))
        else:
            hline.append(p)

    return hline


class LinkParser(HTMLParser):
    def reset(self):
        super().reset()
        self.links = []

    def handle_starttag(self, tag, attrs):
        if tag == "a":
            href, title = None, None
            for name, value in attrs:
                if name == "href":
                    href = value
                if name == "title":
                    title = value
            if href:
                self.links.append((href, title))


def parse_content_links(content):
    """Parse <a> tags from status's `content` and return them as a list of
    (href, title), where `title` may be None.
    """
    parser = LinkParser()
    parser.feed(content)
    return parser.links[:]


def copy_to_clipboard(screen: urwid.raw_display.Screen, text: str):
    """ copy text to clipboard using OSC 52
    This escape sequence is documented
    here https://iterm2.com/documentation-escape-codes.html
    It has wide support - XTerm, Windows Terminal,
    Kitty, iTerm2, others. Some terminals may require a setting to be
    enabled in order to use OSC 52 clipboard functions.
    """

    text_bytes = text.encode("utf-8")
    b64_bytes = base64.b64encode(text_bytes)
    b64_text = b64_bytes.decode("utf-8")

    screen.write(f"\033]52;c;{b64_text}\a")
    screen.flush()


def get_max_toot_chars(instance, default=500):
    # Mastodon
    # https://docs.joinmastodon.org/entities/Instance/#max_characters
    max_toot_chars = deep_get(instance, ["configuration", "statuses", "max_characters"])
    if isinstance(max_toot_chars, int):
        return max_toot_chars

    # Pleroma
    max_toot_chars = instance.get("max_toot_chars")
    if isinstance(max_toot_chars, int):
        return max_toot_chars

    return default


def deep_get(adict: dict, path: List[str], default=None):
    return reduce(
        lambda d, key: d.get(key, default) if isinstance(d, dict) else default,
        path,
        adict
    )


class LRUCache(OrderedDict):
    """Dict with a limited size, ejecting LRUs as needed.
        Default max size = 10Mb"""

    def __init__(self, *args, cache_max_bytes: int = 1024 * 1024 * 10, **kwargs):
        assert cache_max_bytes > 0
        self.total_value_size = 0
        self.cache_max_bytes = cache_max_bytes

        super().__init__(*args, **kwargs)

    def __setitem__(self, key: str, value):
        if key in self:
            self.total_value_size -= sys.getsizeof(super().__getitem__(key).tobytes())
        self.total_value_size += sys.getsizeof(value.tobytes())
        super().__setitem__(key, value)
        super().move_to_end(key)

        while self.total_value_size > self.cache_max_bytes:
            old_key, value = next(iter(self.items()))
            sz = sys.getsizeof(value.tobytes())
            super().__delitem__(old_key)
            self.total_value_size -= sz

    def __getitem__(self, key: str):
        val = super().__getitem__(key)
        super().move_to_end(key)
        return val
