# src/prism/ui.py

import re
from typing import Any, Dict

from rich.align import Align
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from sqlalchemy import Enum as SQLAlchemyEnum
from sqlalchemy import Table as SQLTable

from prism.core.models.tables import TableMetadata
from prism.core.types.utils import JSONBType, get_python_type

# --- Global Console ---
console = Console()

# --- Helper Function ---


def _get_base_type(type_: Any) -> str:
    """Extracts the base type name from a Python type object or string."""
    type_str = str(type_)
    if "typing.Optional" in type_str or "Union" in type_str:
        # Extract from Optional[<type>] or Union[<type>, None]
        match = re.search(r"\[(.*), None\]|\[(.*)\]", type_str)
        if match:
            # Get the first non-None group from the match
            base_type_str = next((g for g in match.groups() if g is not None), "")
            return base_type_str.split(".")[-1]

    if isinstance(type_, type):
        return type_.__name__
    return type_str


# --- UI Display Functions ---
def display_table_structure(table_meta: TableMetadata) -> None:
    """Prints detailed table/view structure from TableMetadata."""
    console.print(f"  [cyan]{table_meta.schema}[/].[cyan bold]{table_meta.name}[/]:")

    for column in table_meta.columns:
        name_str = f"    {column.name:<24}"
        nullable_str = "[red]*[/]" if not column.is_nullable else " "

        # 1. Use the now-correct sql_type from ColumnMetadata
        sql_type_str = f"{str(column.sql_type):<20}"

        # 2. Get the equivalent Python type
        py_type = get_python_type(str(column.sql_type), nullable=column.is_nullable)

        # 3. Use the reliable `column.enum_info` to get the Python type name for display
        if isinstance(py_type, JSONBType):
            python_type_str = f"{'JSONB':<15}"
        elif column.enum_info:
            python_type_str = (
                f"[/][bold yellow]{column.enum_info.name:<15}"  # Display enum name
            )
        else:
            python_type_str = f"{_get_base_type(py_type):<15}"

        python_type_str = f"[violet]{python_type_str}[/]"

        # 4. Build the flags based on the reliable data
        flags = []
        if column.is_pk:
            flags.append("[green]PK[/]")
        if column.foreign_key:
            fk = column.foreign_key
            flags.append(f"[cyan]FK -> {fk.schema}.[bold]{fk.table}[/bold][/]")
        if column.enum_info:
            flags.append(
                f"[yellow dim]({', '.join(map(str, column.enum_info.values))})[/]"
            )

        flags_str = " ".join(flags)

        line = f"{name_str}{nullable_str} [dim]{sql_type_str}[/] {python_type_str} {flags_str}"
        console.print(line)
    console.print()


def display_function_structure(fn_metadata: Any) -> None:
    """Prints detailed function/procedure structure using rich."""
    return_type = fn_metadata.return_type or "void"
    fn_type = str(fn_metadata.type).split(".")[-1].upper()
    console.print(
        f"  [bold]Returns[/bold]: [magenta]{return_type}[/] [dim]({fn_type})[/dim]"
    )

    if fn_metadata.description:
        console.print(f"  [dim]{fn_metadata.description}[/dim]")

    if fn_metadata.parameters:
        params_table = Table(box=None, show_header=False, padding=(0, 1, 0, 4))
        params_table.add_column("Name", style="cyan", width=22)
        params_table.add_column("Type", style="green", width=28)
        params_table.add_column("Details", style="white")

        for param in fn_metadata.parameters:
            mode_str = f"[bold yellow]{param.mode}[/bold yellow]"
            default_str = (
                f" [dim]DEFAULT {param.default_value}[/dim]"
                if param.has_default
                else ""
            )
            params_table.add_row(param.name, param.type, f"{mode_str}{default_str}")
        console.print(params_table)
    console.print()


def _get_operation_id(path: str, name: str, method: str) -> str:
    """
    Correctly predicts the operation_id generated by FastAPI.
    Example: path='/dt/{schema}/tables', name='get_tables' -> 'get_tables_dt__schema__tables_get'
    """

    path_components = path.strip("/").split("/")

    processed_components = []
    for component in path_components:
        if component.startswith("{") and component.endswith("}"):
            processed_components.append(f"__{component[1:-1]}__")
        else:
            processed_components.append(component)

    path_part = "_".join(processed_components).replace("___", "__")

    return f"{name}_{path_part}_{method.lower()}"


def display_route_links(
    db_client: "DbClient",
    title: str,
    tag: str,
    endpoints: Dict[str, tuple[str, str, str]],
    port: int = 8000,
) -> None:
    """
    Constructs and prints link-enabled documentation summary for a group of routes.

    Args:
        db_client: The database client instance to get host info.
        title: The title for the route group (e.g., "Health API").
        tag: The OpenAPI tag for this route group (e.g., "Health").
        endpoints: A dictionary of {description: (path_suffix, function_name, http_method)}.
        port: The server port.
    """
    # Get host directly from the engine's URL
    host = db_client.engine.url.host or "127.0.0.1"
    docs_base_url = f"http://{host}:{port}/docs"
    tag_link = f"{docs_base_url}#/{tag}"

    console.print(
        Text.from_markup(
            f"  [bold]{title} available.[/] Main Docs: [link={tag_link}]{tag}[/link]"
        )
    )

    for description, (path, func_name, method) in endpoints.items():
        operation_id = _get_operation_id(path, func_name, method)
        full_docs_link = f"{docs_base_url}#/{tag}/{operation_id}"

        console.print(
            Text.from_markup(
                f"    {description:<25} [link={full_docs_link}][dim]({method.upper()} {path})[/dim][/link]"
            )
        )
    console.print()


def print_welcome(project_name: str, version: str, host: str, port: int) -> None:
    """Prints a welcome message using a rich Panel."""
    docs_url = f"http://{host}:{port}/docs"
    message = Text.from_markup(
        f"API Documentation available at [link={docs_url}]{docs_url}[/link]"
    )
    panel = Panel(
        Align.center(message, vertical="middle"),
        title=f"[bold green]{project_name} v{version}[/bold green]",
        border_style="blue",
        padding=(1, 2),
    )
    console.print(panel)
