# Rich reference: https://rich.readthedocs.io/en/latest/reference.html
import json
from copy import deepcopy
from datetime import datetime
from os import devnull
from pathlib import Path
from typing import Mapping, Sequence

from rich import box
from rich.align import Align
from rich.console import Console, Group, RenderableType
from rich.markup import escape
from rich.panel import Panel
from rich.padding import Padding
from rich.table import Table
from rich.text import Text
from rich.theme import Theme

from epstein_files.output.epstein_highlighter import highlighter
from epstein_files.output.highlight_config import HIGHLIGHT_GROUPS
from epstein_files.util.constant.strings import *
from epstein_files.util.constant.urls import *
from epstein_files.util.env import args, site_config
from epstein_files.util.helpers.data_helpers import json_safe, sort_dict
from epstein_files.util.helpers.link_helper import link_markup
from epstein_files.util.helpers.rich_helpers import enclose
from epstein_files.util.logging import logger

NA_TXT = Text(NA, style='dim')
SUBTITLE_PADDING = (1, 0, 1, 0)
GREY_NUMBERS = [58, 39, 39, 35, 30, 27, 23, 23, 19, 19, 15, 15, 15]
VALID_GREYS = [0, 3, 7, 11, 15, 19, 23, 27, 30, 35, 37, 39, 42, 46, 50, 53, 54, 58, 62, 63, 66, 69, 70, 74, 78, 82, 84, 85, 89, 93]

LINK_HREF_LINE_REGEX = re.compile(r"^([>• ]*)(http\S+)(.*)")

DATASET_DESCRIPTION_STYLE = 'gray74'
# INFO_STYLE = 'light_goldenrod2 italic'
INFO_STYLE = 'gray50 italic'
KEY_STYLE = 'dim'
KEY_STYLE_ALT = 'light_steel_blue3'
LAST_TIMESTAMP_STYLE = 'wheat4'
PATH_STYLE = 'deep_pink3'
STR_VAL_STYLE = 'cornsilk1 italic'
STR_VAL_STYLE_ALT = 'light_yellow3 italic'
SYMBOL_STYLE = 'grey70'
TABLE_BORDER_STYLE = 'grey46'
TABLE_TITLE_STYLE = f"gray54 italic"
TITLE_STYLE = 'black on white'  # color(103)'
WARNING_STYLE = 'bold black on white'

DEFAULT_TABLE_KWARGS = {
    'border_style': TABLE_BORDER_STYLE,
    'caption_style': 'navajo_white3 dim italic',
    'header_style': "bold",
    'title_style': TABLE_TITLE_STYLE,
}

THEME_STYLES = {
    DEFAULT: 'wheat4',
    TEXT_LINK: 'deep_sky_blue4 underline',
    # f"{REGEX_STYLE_PREFIX}.{HIGHLIGHTED_QUOTE}": 'on gray27',
    f"{REGEX_STYLE_PREFIX}.{HIGHLIGHTED_QUOTE}": 'honeydew2 italic on gray15',
    f"{REGEX_STYLE_PREFIX}.reverse": 'reverse',
    **{hg.theme_style_name: hg.style for hg in HIGHLIGHT_GROUPS},
}

RAINBOW = [
    'royal_blue1',
    'medium_purple',
    'light_coral',
    'light_slate_gray',
    'dark_goldenrod',
    'wheat4',
    'white',
    'medium_orchid',
    'deep_pink1',
    'navajo_white1',
]

RICH_THEME = Theme(THEME_STYLES)

# Instantiate console object
CONSOLE_KWARGS = {
    'color_system': '256',
    'highlighter': highlighter,
    'record': args.build,
    'safe_box': True,
    'theme': RICH_THEME,
    'width': args.width,
}

if args.suppress_output:
    logger.warning(f"Suppressing terminal output because args.suppress_output={args.suppress_output}...")
    CONSOLE_KWARGS.update({'file': open(devnull, "wt")})

console = Console(**CONSOLE_KWARGS)


def add_cols_to_table(table: Table, cols: list[str | dict], justify: str = 'center') -> None:
    """Left most col will be left justified, rest are center justified."""
    for i, col in enumerate(cols):
        col_justify = 'left' if i == 0 else justify

        if isinstance(col, dict):
            col_name = col['name']
            col_kwargs = deepcopy(col)
            col_kwargs['justify'] = col_kwargs.get('justify', col_justify)
            del col_kwargs['name']
        else:
            col_name = col
            col_kwargs = {'justify': col_justify}

        table.add_column(col_name, **col_kwargs)


def bool_txt(b: bool | None, match_width: bool = False) -> Text:
    txt = Text('')

    if b is False:
        return txt.append(str(b), style='bright_red bold italic')
    elif b is True:
        return txt.append(f" {b}" if match_width else str(b), style='bright_green bold italic')
    else:
        return txt.append(str(b), style='dim italic')


def build_table(
    title: str | Text | None,
    cols: list[str | dict] | None = None,
    copy_props_from: Table | None = None,
    **kwargs
) -> Table:
    table = Table(title=title, **{**DEFAULT_TABLE_KWARGS, **kwargs})

    if copy_props_from:
        for k, v in vars(copy_props_from).items():
            if k.startswith('_') or k in ['columns', 'rows']:
                continue

            setattr(table, k, v)

    if cols:
        add_cols_to_table(table, cols)

    return table


def create_hyperlinks(line: str) -> Text:
    """Add [link] tags if appropriate."""
    if (match := LINK_HREF_LINE_REGEX.match(line)):
        logger.debug(f"Creating hyperlink for '{line}'")
        link = match.group(2)
        txt = Text(match.group(1))
        txt.append(Text.from_markup(f"[link={link}]{link}[/link]"))
        return txt.append(match.group(3))
    else:
        return Text(line)


def indent_txt(txt: str | Text, spaces: int = 4, prefix: str = '') -> Text:
    indent = Text(' ' * spaces).append(prefix)
    return indent + Text(f"\n{indent}").join(txt.split('\n'))


def join_texts(txts: Sequence[str | Text], join: str = ' ', encloser: str = '', encloser_style: str = 'wheat4') -> Text:
    """Join rich.Text objs into one."""
    txt = Text('')

    for i, _txt in enumerate(txts):
        txt.append(join if i >= 1 else '').append(enclose(_txt, encloser, encloser_style))

    return txt


def left_indent(obj: RenderableType, num_spaces: int) -> Padding:
    return Padding(obj, left_indent_padding(num_spaces))


def prefix_with(txt: list[str] | list[Text] | Text | str, pfx: str, pfx_style: str = '', indent: str | int = '') -> Text:
    indent = indent * ' ' if isinstance(indent, int) else indent

    lines = [
        Text('').append(f"{indent}{pfx} ", style=pfx_style).append(line)
        for line in (txt.split('\n') if isinstance(txt, (Text, str)) else txt)
    ]

    return Text('\n').join(lines)


def print_centered(obj: RenderableType, style: str = '') -> None:
    if args.mobile:
        if isinstance(obj, str):
            obj = Text.from_markup(obj, justify='center')
        elif isinstance(obj, Text):
            obj = Text('', justify='center').append(obj)

    console.print(Align.center(obj), highlight=False, style=style)


def print_centered_link(url: str, link_text: str, style: str | None = None) -> None:
    print_centered(link_text_obj(url, link_text, style or ARCHIVE_LINK_COLOR))


def print_json(label: str, obj: object, skip_falsey: bool = False) -> None:
    if isinstance(obj, dict):
        if skip_falsey:
            obj = {k: v for k, v in obj.items() if v}

        obj = json_safe(obj)

    console.line()
    console.print(Panel(label, expand=False))
    console.print_json(json.dumps(obj, sort_keys=True), indent=4)
    console.line()


def print_special_note(note: str | Text) -> None:
    """Print an eye catching panel with extra info about a person."""
    print_centered(Padding(Panel(note, expand=True, padding=(1, 3), style='reverse'), (0, 0, 2, 0)))


def print_subtitle_panel(msg: str, style: str = 'black on white', center: bool = True) -> None:
    """A reverse color panel to put at the top of sections."""
    panel = Panel(
        Text.from_markup(msg, justify='center' if center else 'left'),
        width=site_config.subtitle_width if center else max(len(msg), 150),
        style=style if center else ''
    )

    if center:
        print_centered(Padding(panel, SUBTITLE_PADDING))
    else:
        console.print(panel)


def quote_txt(t: Text | str, try_double_quote_first: bool = False, style: str = '') -> Text:
    if try_double_quote_first:
        quote_char = '"' if '"' not in t else "'"
    else:
        quote_char = "'" if "'" not in t else '"'

    return Text(quote_char, style=style).append(t).append(quote_char)


def styled_dict(
    d: Mapping[str, bool | datetime | str | Path | Text | None],
    key_style: str = KEY_STYLE,
    sep: str = ': ',
    sort_fields: bool = True,
    min_indent: int = 20,
) -> Text:
    """Turn a dict into a colored representation."""
    key_lengths = [len(k) for k in d.keys()] + [min_indent]
    key_prefixes = list(set([k.split('.')[0] for k in d.keys() if '.' in k]))
    key_pfx_colors = {k: RAINBOW[i] for i, k in enumerate(key_prefixes)}

    key_colors = {
        k: key_pfx_colors.get(k.split('.')[0] if '.' in k else k, key_style)
        for k in d.keys()
    }

    return Text('\n').join([
        styled_key_value(k, v, key_style=key_colors.get(k, key_style), indent=max(key_lengths) + 3, sep=sep)
        for k, v in (sort_dict(d) if sort_fields else d.items())
    ])


def styled_key_value(
    key: str,
    val: bool | datetime | int | str | Path | Text | None,
    key_style: str = KEY_STYLE,
    indent: int = 0,
    sep='='
) -> Text:
    """Generate `Text` for 'key=value'."""
    if '.' in key and key_style == KEY_STYLE:
        key_style = KEY_STYLE_ALT

    if val is None:
        val_txt = Text('None', style='dim italic')
    elif isinstance(val, Text):
        val_txt = val
    elif isinstance(val, list):
        val_txt = highlighter(json.dumps(val))
    elif isinstance(val, bool):
        val_txt = bool_txt(val)
    elif isinstance(val, datetime):
        val_txt = Text(str(val), style=TIMESTAMP_STYLE)
    elif isinstance(val, dict):
        val_txt = indent_txt(Text('\n').append(styled_dict(val)), 14)
    else:
        val_txt = None
        val_style = ''

        if isinstance(val, int):
            val = str(val)
            val_style = 'cyan'
        elif isinstance(val, Path):
            val = val = f"'{val}'"
            val_style = PATH_STYLE
        elif isinstance(val, str):
            if val.startswith('http'):
                val_style = ARCHIVE_LINK_UNDERLINE
            elif key.endswith('category') or key == AUTHOR:
                val_txt = highlighter(val)
            elif key.endswith('filename'):
                val_style = PATH_STYLE
            elif key.endswith('style'):
                val_style = f"{val} bold"
            elif key.endswith('_type'):
                val_style = 'light_slate_gray bold'
            elif key.endswith('id'):
                val_style = 'cyan'
            else:
                val_style = STR_VAL_STYLE_ALT if '.' in key else STR_VAL_STYLE
                val_txt = quote_txt(highlighter(val), style=val_style)

        val_txt = val_txt or Text(str(val), style=val_style or 'bright_white')

    txt = Text('').append(f"{key:>{indent}}", style=key_style)
    txt.append(sep, style=SYMBOL_STYLE).append(val_txt)
    return txt


def wrap_in_markup_style(msg: str, style: str | None = None) -> str:
    if style is None or len(style.strip()) == 0:
        return msg

    modifier = ''

    for style_word in style.split():
        if style_word == 'on':
            modifier = style_word
            continue

        style = f"{modifier} {style_word}".strip()
        msg = f"[{style}]{msg}[/{style}]"
        modifier = ''

    return msg


if args.colors_only and args.debug:
    print_json('THEME_STYLES', THEME_STYLES)
