"""UI utilities for OCLAWMA CLI.

Provides consistent styling, progress indicators, and error formatting
across all CLI commands.
"""

from __future__ import annotations

import sys
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TYPE_CHECKING

import click

if TYPE_CHECKING:
    from collections.abc import Callable


# Color scheme for consistent styling
class Colors:
    """ANSI color constants for terminal output."""

    SUCCESS = "green"
    ERROR = "red"
    WARNING = "yellow"
    INFO = "cyan"
    MUTED = "bright_black"
    ACCENT = "magenta"
    HIGHLIGHT = "blue"


class Icons:
    """Unicode icons for visual indicators."""

    SUCCESS = "✓"
    ERROR = "✗"
    WARNING = "⚠"
    INFO = "ℹ"
    BULLET = "•"
    ARROW = "→"
    SPARKLES = "✨"
    ROCKET = "🚀"
    GEAR = "⚙"
    LOCK = "🔒"
    FOLDER = "📁"
    FILE = "📄"
    CHART = "📊"
    TOOLS = "🔧"
    BOOK = "📚"
    LIGHTBULB = "💡"
    MAGNIFYING_GLASS = "🔍"
    PACKAGE = "📦"
    TRASH = "🗑"


def success(text: str, icon: bool = True) -> str:
    """Format success message.

    Args:
        text: Message text
        icon: Whether to include success icon

    Returns:
        Styled text
    """
    prefix = f"{Icons.SUCCESS} " if icon else ""
    return click.style(f"{prefix}{text}", fg=Colors.SUCCESS, bold=True)


def error(text: str, icon: bool = True) -> str:
    """Format error message.

    Args:
        text: Message text
        icon: Whether to include error icon

    Returns:
        Styled text
    """
    prefix = f"{Icons.ERROR} " if icon else ""
    return click.style(f"{prefix}{text}", fg=Colors.ERROR, bold=True)


def warning(text: str, icon: bool = True) -> str:
    """Format warning message.

    Args:
        text: Message text
        icon: Whether to include warning icon

    Returns:
        Styled text
    """
    prefix = f"{Icons.WARNING} " if icon else ""
    return click.style(f"{prefix}{text}", fg=Colors.WARNING)


def info(text: str, icon: bool = False) -> str:
    """Format info message.

    Args:
        text: Message text
        icon: Whether to include info icon

    Returns:
        Styled text
    """
    prefix = f"{Icons.INFO} " if icon else ""
    return click.style(f"{prefix}{text}", fg=Colors.INFO)


def muted(text: str) -> str:
    """Format muted/secondary text.

    Args:
        text: Message text

    Returns:
        Styled text
    """
    return click.style(text, fg=Colors.MUTED)


def accent(text: str, bold: bool = False) -> str:
    """Format accent/highlighted text.

    Args:
        text: Message text
        bold: Whether to make text bold

    Returns:
        Styled text
    """
    return click.style(text, fg=Colors.ACCENT, bold=bold)


def highlight(text: str, bold: bool = False) -> str:
    """Format highlighted text.

    Args:
        text: Message text
        bold: Whether to make text bold

    Returns:
        Styled text
    """
    return click.style(text, fg=Colors.HIGHLIGHT, bold=bold)


def header(text: str, width: int = 60) -> str:
    """Format a section header.

    Args:
        text: Header text
        width: Width of the header

    Returns:
        Styled header string
    """
    padding = (width - len(text) - 2) // 2
    line = "═" * width
    centered = " " * padding + text + " " * padding
    return f"{click.style(line, fg=Colors.ACCENT)}\n{click.style(centered, fg=Colors.ACCENT, bold=True)}\n{click.style(line, fg=Colors.ACCENT)}"


def subheader(text: str) -> str:
    """Format a subsection header.

    Args:
        text: Header text

    Returns:
        Styled header string
    """
    return f"{click.style('─' * 58, fg=Colors.MUTED)}\n{click.style(text, fg=Colors.ACCENT, bold=True)}"


def bullet(text: str, level: int = 0) -> str:
    """Format a bullet point.

    Args:
        text: Item text
        level: Indentation level

    Returns:
        Styled bullet string
    """
    indent = "  " * level
    return f"{indent}{click.style(Icons.BULLET, fg=Colors.ACCENT)} {text}"


def key_value(key: str, value: str, key_width: int = 20) -> str:
    """Format a key-value pair.

    Args:
        key: Key name
        value: Value text
        key_width: Width for key alignment

    Returns:
        Styled key-value string
    """
    key_formatted = click.style(f"{key}:", fg=Colors.MUTED).ljust(key_width + 10)
    return f"  {key_formatted} {value}"


class ErrorHelper:
    """Helper for formatting contextual error messages."""

    @staticmethod
    def config_not_found(path: str) -> str:
        """Error message for missing config."""
        return (
            f"{error(f'Configuration not found: {path}')}\n"
            f"\n"
            f"{info('To create a default configuration:')}\n"
            f"  {highlight('oclawma config init')}\n"
            f"\n"
            f"{muted('Or set a custom config path with:')}\n"
            f"  {muted('oclawma --config-path /path/to/config.yaml')}"
            ""
        )

    @staticmethod
    def provider_not_available(provider: str, details: str = "") -> str:
        """Error message for unavailable provider."""
        provider_label = f'Provider "{provider}" is not available'
        msg = f"{error(provider_label)}"
        if details:
            msg += f"\n\n{warning(details)}"
        msg += f"\n\n{info('Available providers:')}\n"
        msg += f"  {bullet('ollama - Local LLM provider (requires Ollama)')}\n"
        msg += f"  {bullet('kimi - Cloud provider (requires KIMI_API_KEY)')}\n"
        msg += f"  {bullet('auto - Automatic fallback')}\n\n"
        msg += f"{muted('Set provider with:')}\n"
        msg += f"  {muted('oclawma config set active_profile <profile>')}"
        return msg

    @staticmethod
    def api_key_missing(provider: str, env_var: str) -> str:
        """Error message for missing API key."""
        return (
            f"{error(f'{provider} requires an API key')}\n"
            f"\n"
            f"{info('Set the environment variable:')}\n"
            f"  {highlight(f'export {env_var}=your-api-key')}\n"
            f"\n"
            f"{muted('Or configure in your config file:')}\n"
            f"  {muted('oclawma config set api_key_env YOUR_API_KEY --profile ' + provider.lower())}"
        )

    @staticmethod
    def connection_failed(url: str, suggestion: str = "") -> str:
        """Error message for connection failures."""
        msg = (
            f"{error(f'Failed to connect to: {url}')}\n"
            f"\n"
            f"{warning('Possible causes:')}\n"
            f"  {bullet('Service is not running')}\n"
            f"  {bullet('Incorrect URL or port')}\n"
            f"  {bullet('Network connectivity issues')}\n"
            f"  {bullet('Firewall blocking the connection')}\n"
        )
        if suggestion:
            msg += f"\n{info('Suggestion:')}\n  {suggestion}"
        return msg

    @staticmethod
    def profile_not_found(name: str, available: list[str]) -> str:
        """Error message for missing profile."""
        profile_label = f'Profile "{name}" not found'
        msg = f"{error(profile_label)}\n\n"
        if available:
            msg += f"{info('Available profiles:')}\n"
            for p in available:
                msg += f"  {bullet(p)}\n"
        msg += f"\n{muted('Create a new profile:')}\n"
        msg += f"  {muted(f'oclawma config profile add {name} --provider <provider>')}"
        return msg

    @staticmethod
    def skill_not_found(name: str) -> str:
        """Error message for missing skill."""
        skill_label = f'Skill "{name}" not found'
        return (
            f"{error(skill_label)}\n"
            f"\n"
            f"{info('Search for available skills:')}\n"
            f"  {highlight('oclawma skills search')}\n"
            f"\n"
            f"{info('List installed skills:')}\n"
            f"  {highlight('oclawma skills list')}"
        )

    @staticmethod
    def permission_denied(operation: str, path: str = "") -> str:
        """Error message for permission issues."""
        msg = f"{error(f'Permission denied: {operation}')}"
        if path:
            msg += f"\n\n  Path: {highlight(path)}"
        msg += f"\n\n{info('Possible solutions:')}\n"
        msg += f"  {bullet('Check file permissions with ls -la')}\n"
        msg += f"  {bullet('Run with appropriate privileges')}\n"
        msg += f"  {bullet('Check if the path is correct')}"
        return msg


@contextmanager
def progress_spinner(label: str = "Working...") -> Iterator[Callable[[str], None]]:
    """Context manager for a simple progress spinner.

    Args:
        label: Initial label to display

    Yields:
        Function to update the label

    Example:
        with progress_spinner("Loading...") as update:
            update("Processing step 1...")
            do_step_1()
            update("Processing step 2...")
            do_step_2()
    """
    import itertools
    import threading
    import time

    spinner = itertools.cycle(["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
    current_label = [label]
    stop_event = threading.Event()

    def spin() -> None:
        while not stop_event.is_set():
            char = next(spinner)
            sys.stdout.write(f"\r{click.style(char, fg=Colors.ACCENT)} {current_label[0]}")
            sys.stdout.flush()
            time.sleep(0.1)
        sys.stdout.write("\r" + " " * (len(current_label[0]) + 2) + "\r")
        sys.stdout.flush()

    def update(new_label: str) -> None:
        current_label[0] = new_label

    thread = threading.Thread(target=spin)
    thread.start()

    try:
        yield update
    finally:
        stop_event.set()
        thread.join()


@contextmanager
def progress_bar(
    label: str = "Processing",
    length: int = 100,
    show_eta: bool = True,
) -> Iterator[Callable[[int], None]]:
    """Context manager for a progress bar.

    Args:
        label: Label to display
        length: Total length of progress
        show_eta: Whether to show ETA

    Yields:
        Function to update progress (pass current position)

    Example:
        with progress_bar("Uploading", length=file_size) as update:
            for chunk in upload():
                update(uploaded_bytes)
    """
    with click.progressbar(
        length=length,
        label=click.style(label, fg=Colors.INFO),
        show_eta=show_eta,
        bar_template=f"{click.style('%(label)s', fg=Colors.INFO)} [{click.style('%(bar)s', fg=Colors.ACCENT)}] {click.style('%(info)s', fg=Colors.MUTED)}",
        fill_char=click.style("█", fg=Colors.ACCENT),
        empty_char=click.style("░", fg=Colors.MUTED),
    ) as bar:
        yield bar.update


def print_success(message: str) -> None:
    """Print a success message."""
    click.echo(success(message))


def print_error(message: str) -> None:
    """Print an error message."""
    click.echo(error(message), err=True)


def print_warning(message: str) -> None:
    """Print a warning message."""
    click.echo(warning(message))


def print_info(message: str) -> None:
    """Print an info message."""
    click.echo(info(message, icon=True))


def confirm_prompt(text: str, default: bool = False) -> bool:
    """Styled confirmation prompt.

    Args:
        text: Prompt text
        default: Default value

    Returns:
        True if confirmed, False otherwise
    """
    return click.confirm(
        click.style(text, fg=Colors.WARNING),
        default=default,
    )


def prompt_input(text: str, default: str | None = None, hide_input: bool = False) -> str:
    """Styled input prompt.

    Args:
        text: Prompt text
        default: Default value
        hide_input: Whether to hide input (for passwords)

    Returns:
        User input
    """
    prompt_text = click.style(text, fg=Colors.INFO)
    if default:
        prompt_text += click.style(f" [{default}]", fg=Colors.MUTED)
    return click.prompt(prompt_text, default=default, hide_input=hide_input)


def format_duration(seconds: float) -> str:
    """Format duration in human-readable form.

    Args:
        seconds: Duration in seconds

    Returns:
        Formatted duration string
    """
    if seconds < 60:
        return f"{seconds:.1f}s"
    elif seconds < 3600:
        minutes = seconds / 60
        return f"{minutes:.1f}m"
    else:
        hours = seconds / 3600
        return f"{hours:.1f}h"


def format_bytes(bytes_value: int) -> str:
    """Format bytes in human-readable form.

    Args:
        bytes_value: Size in bytes

    Returns:
        Formatted size string
    """
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if bytes_value < 1024:
            return f"{bytes_value:.1f} {unit}"
        bytes_value /= 1024
    return f"{bytes_value:.1f} PB"


def format_number(num: int) -> str:
    """Format large numbers with commas.

    Args:
        num: Number to format

    Returns:
        Formatted number string
    """
    return f"{num:,}"
