from __future__ import annotations

import argparse
import json
import sys
import tempfile
from functools import singledispatch
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List

from rich.traceback import install
from typing_extensions import TypedDict

from . import calendar, task
from .fields import get_val
from .generic import flexitable
from .github import pulls_table
from .music import albums_table
from .utils import make_console, new_table, pretty_diff, wrap

if TYPE_CHECKING:
    from rich.console import RenderableType
    from rich.table import Table

JSONDict = Dict[str, Any]


console = make_console()
install(console=console, show_locals=True, width=console.width)


def lights_table(lights: List[JSONDict], **__) -> Iterator[Table]:
    from rgbxy import Converter

    headers = lights[0].keys()
    table = new_table(*headers)
    conv = Converter()
    for light in lights:
        xy = light.get("xy")
        style = ""
        if not light["on"]:
            style = "dim"
            light["xy"] = ""
        elif xy:
            color = conv.xy_to_hex(*xy)
            light["xy"] = wrap("   a", f"#{color} on #{color}")
        table.add_row(*(get_val(light, h) for h in headers), style=style)
    yield table


def load_data(filepath: str) -> list[JSONDict] | JSONDict | str:
    """Load data from the given file.

    Try to load the data as JSON, otherwise return the text as is.
    """
    if filepath == "/dev/stdin":
        text = sys.stdin.read()
    else:
        path = Path(filepath)
        text = path.read_text() if path.exists() and path.is_file() else filepath

    try:
        json_data: JSONDict | list[JSONDict] = json.loads(text)
    except json.JSONDecodeError:
        return text
    else:
        return json_data


def get_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="""Pretty-print JSON data.
By default, read JSON data from stdin and prettify it.
Otherwise, use command 'diff' to compare two JSON objects or blocks of text.""",
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument("-v", "--verbose", action="store_true")
    parser.add_argument("-j", "--json", action="store_true", help="output as JSON")
    parser.add_argument(
        "-s", "--save", action="store_true", help="save the output as HTML"
    )

    subparsers = parser.add_subparsers(
        dest="command", title="Subcommands", required=False
    )
    diff_parser = subparsers.add_parser("diff", help="show diff between two objects")
    diff_parser.add_argument(
        "before", type=load_data, help="JSON object or text before, can be a filename"
    )
    diff_parser.add_argument(
        "after", type=load_data, help="JSON object or text after, can be a filename"
    )
    return parser.parse_args()


class NamedData(TypedDict):
    title: str
    values: list[JSONDict]


TABLE_BY_NAME: Dict[str, Callable[..., Any]] = {
    "Pull Requests": pulls_table,
    "Hue lights": lights_table,
    "Calendar": calendar.get_table,
    "Album": albums_table,
    "Tasks": task.get_table,
}


@singledispatch
def draw_data(data: Any, **kwargs) -> Iterator[RenderableType]:
    """Render the provided data."""
    yield data


@draw_data.register(dict)
def _draw_data_dict(data: JSONDict | NamedData, **kwargs) -> Iterator[RenderableType]:
    if (title := data.get("title")) and (values := data.get("values")):
        table = TABLE_BY_NAME.get(title, flexitable)
        yield from table(values, **kwargs)
    else:
        yield flexitable(data)


@draw_data.register(list)
def _draw_data_list(data: list[JSONDict], **kwargs) -> Iterator[RenderableType]:
    yield flexitable(data)


def main() -> None:
    args = get_args()
    if args.command == "diff":
        console.print(pretty_diff(args.before, args.after))
    else:
        data = load_data("/dev/stdin")
        if args.json:
            console.print_json(data=data)
        else:
            console.record = True
            for renderable in filter(None, draw_data(data, verbose=args.verbose)):
                console.print(renderable)

    if args.save:
        filename = tempfile.NamedTemporaryFile(suffix=".html", delete=False).name
        console.save_html(filename)
        print(f"Saved output as {filename}", file=sys.stderr)


if __name__ == "__main__":
    main()
