from functools import reduce
from itertools import zip_longest
from typing import List, Set, Tuple, Union

from polylith import output
from polylith.reporting import theme
from rich import box
from rich.console import Console
from rich.padding import Padding
from rich.table import Table


def to_col(brick: str, tag: str) -> str:
    name = "\n".join(brick)

    return f"[{tag}]{name}[/]"


def brick_status(bricks: Set[str], brick_name: str, imported: str) -> str:
    status = theme.check_emoji if imported in bricks and imported != brick_name else "-"

    return f"[data]{status}[/]"


def to_row(name: str, tag: str, brick_imports: dict, imported: List[str]) -> List[str]:
    bricks = brick_imports.get(name) or set()
    statuses = [brick_status(bricks, name, i) for i in imported]

    return [f"[{tag}]{name}[/]"] + statuses


def flatten_import(acc: Set[str], kv: Tuple[str, Set[str]]) -> set:
    key = kv[0]
    values = kv[1]

    return set().union(acc, values.difference({key}))


def flatten_imports(brick_imports: dict) -> Set[str]:
    """Flatten the dict into a set of imports, with the actual brick filtered away when existing as an import"""
    return reduce(flatten_import, brick_imports.items(), set())


def create_columns(
    imported_bases: List[str], imported_components: List[str]
) -> List[str]:
    base_cols = [to_col(brick, "base") for brick in imported_bases]
    comp_cols = [to_col(brick, "comp") for brick in imported_components]

    return comp_cols + base_cols


def create_rows(
    bases: Set[str], components: Set[str], import_data: dict, imported: List[str]
) -> List[List[str]]:
    base_rows = [to_row(b, "base", import_data, imported) for b in sorted(bases)]
    comp_rows = [to_row(c, "comp", import_data, imported) for c in sorted(components)]

    return comp_rows + base_rows


def find_longest_brick_name(bases: Set[str], components: Set[str]) -> str:
    bricks = set().union(bases, components)

    return max(bricks, key=len)


def calculate_brick_column_width(
    bases: Set[str], components: Set[str]
) -> Union[int, None]:
    longest = find_longest_brick_name(bases, components)

    return len(longest)


def print_deps(bricks: dict, import_data: dict, options: dict):
    bases = bricks["bases"]
    components = bricks["components"]
    flattened = flatten_imports(import_data)

    imported_bases = sorted({b for b in flattened if b in bases})
    imported_components = sorted({c for c in flattened if c in components})
    imported_bricks = imported_components + imported_bases
    save = options.get("save", False)

    table = Table(box=box.SIMPLE_HEAD)
    brick_width = calculate_brick_column_width(bases, components)
    table.add_column("[data]brick[/]", width=brick_width)

    cols = create_columns(imported_bases, imported_components)
    rows = create_rows(bases, components, import_data, imported_bricks)

    for col in cols:
        table.add_column(col, justify="center")

    for row in rows:
        table.add_row(*row)

    console = Console(theme=theme.poly_theme)
    console.print(table, overflow="ellipsis")

    if save:
        output.save(table, options, "deps")


def calculate_tag(brick: str, bases: Set[str]) -> str:
    return "base" if brick in bases else "comp"


def print_brick_deps(brick: str, bricks: dict, brick_deps: dict, options: dict):
    bases = bricks["bases"]

    save = options.get("save", False)

    brick_used_by = brick_deps["used_by"]
    brick_uses = brick_deps["uses"]

    tag = calculate_tag(brick, bases)

    console = Console(theme=theme.poly_theme)

    table = Table(box=box.SIMPLE_HEAD)
    table.add_column("[data]used by[/]")
    table.add_column(":backhand_index_pointing_left:")
    table.add_column(f"[{tag}]{brick}[/]")
    table.add_column(":backhand_index_pointing_right:")
    table.add_column("[data]uses[/]")

    for item in zip_longest(brick_used_by, brick_uses):
        used_by, uses = item

        used_by_tag = calculate_tag(used_by, bases) if used_by else ""
        uses_tag = calculate_tag(uses, bases) if uses else ""

        left = f"[{used_by_tag}]{used_by}[/]" if used_by else ""
        right = f"[{uses_tag}]{uses}[/]" if uses else ""

        row = [left, "", "", "", right]

        table.add_row(*row)

    console.print(table, overflow="ellipsis")

    if save:
        output.save(table, options, f"deps_{brick}")


def print_brick_with_circular_deps(brick: str, deps: Set[str], bricks: dict) -> None:
    bases = bricks["bases"]

    console = Console(theme=theme.poly_theme)

    tag = calculate_tag(brick, bases)

    with_tags = [f"[{calculate_tag(name, bases)}]{name}" for name in sorted(deps)]
    others = "[data],[/] ".join(with_tags)

    prefix = ":information:"
    message = f"[{tag}]{brick}[/] [data]is used by[/] {others} [data]and also uses[/] {others}[data].[/]"

    console.print(Padding(f"{prefix} {message}", (0, 0, 0, 1)), overflow="ellipsis")


def print_bricks_with_circular_deps(circular_bricks: dict, bricks: dict) -> None:
    for brick, deps in circular_bricks.items():
        print_brick_with_circular_deps(brick, deps, bricks)
