from typing import TYPE_CHECKING, NoReturn

import typer

from pipelex.cogt.exceptions import ModelDeckPresetValidatonError
from pipelex.core.pipes.exceptions import PipeOperatorModelChoiceError
from pipelex.hub import get_console
from pipelex.pipe_operators.exceptions import PipeOperatorModelAvailabilityError
from pipelex.types import StrEnum
from pipelex.urls import URLs

if TYPE_CHECKING:
    from pipelex.pipeline.validate_bundle import ValidateBundleError


class ErrorContext(StrEnum):
    """Context for error messages in CLI commands."""

    PIPE_RUN = "Pipe run"
    VALIDATION = "Pipe validation"
    BUILD = "Pipe build"

    # Pre-validation contexts (for Pipelex.make() errors)
    VALIDATION_BEFORE_SHOW_PIPES = "Pre-validation (show pipes)"
    VALIDATION_BEFORE_SHOW_PIPE = "Pre-validation (show pipe)"
    VALIDATION_BEFORE_SHOW_MODELS = "Pre-validation (show models)"
    VALIDATION_BEFORE_SHOW_BACKENDS = "Pre-validation (show backends)"
    VALIDATION_BEFORE_PIPE_RUN = "Pre-validation (pipe run)"
    VALIDATION_BEFORE_BUILD_PIPE = "Pre-validation (build pipe)"
    VALIDATION_BEFORE_BUILD_RUNNER = "Pre-validation (build runner)"
    VALIDATION_BEFORE_BUILD_INPUTS = "Pre-validation (build inputs)"
    VALIDATION_BEFORE_BUILD_ONE_SHOT = "Pre-validation (build one-shot)"
    VALIDATION_BEFORE_BUILD_PARTIAL = "Pre-validation (build partial)"


def handle_model_choice_error(exc: PipeOperatorModelChoiceError, context: ErrorContext) -> NoReturn:
    """Handle and display PipeOperatorModelChoiceError with formatted output.

    Args:
        exc: The model choice error exception
        context: Context for the error message
    """
    console = get_console()
    console.print(f"\n[bold red]❌ {context} failed because of a model choice could not be interpreted correctly[/bold red]\n")
    console.print(f"[bold cyan]Pipe:[/bold cyan]         [yellow]'{exc.pipe_code}'[/yellow] [dim]({exc.pipe_type})[/dim]")
    console.print(f"[bold cyan]Model Type:[/bold cyan]   [yellow]'{exc.model_type}'[/yellow]")
    console.print(f"[bold cyan]Model Choice:[/bold cyan] [yellow]'{exc.model_choice}'[/yellow]")
    console.print(f"\n[bold red]Error:[/bold red]        {exc.message}\n")
    console.print(
        f"[bold green]💡 Tip:[/bold green] Check your model configuration in [cyan].pipelex/inference/[/cyan] "
        f"or specify a different model in the [yellow]'{exc.pipe_code}'[/yellow] pipe."
    )
    console.print(f"[dim]Learn more about the inference backend system: {URLs.backend_provider_docs}[/dim]")
    console.print(f"[dim]Join our Discord for help: {URLs.discord}[/dim]\n")
    raise typer.Exit(1) from exc


def handle_model_availability_error(exc: PipeOperatorModelAvailabilityError, context: ErrorContext) -> NoReturn:
    """Handle and display PipeOperatorModelAvailabilityError with formatted output.

    Args:
        exc: The model availability error exception
        context: Context for the error message
    """
    console = get_console()
    console.print(f"\n[bold red]❌ {context} failed because a model wasn't available[/bold red]\n")
    console.print(f"[bold cyan]Pipe:[/bold cyan]         [yellow]'{exc.pipe_code}'[/yellow] [dim]({exc.pipe_type})[/dim]")
    console.print(f"[bold cyan]Model:[/bold cyan]        [yellow]'{exc.model_handle}'[/yellow]")
    if exc.fallback_list:
        fallbacks_str = ", ".join([f"[yellow]{fb}[/yellow]" for fb in exc.fallback_list])
        console.print(f"[bold cyan]Fallbacks:[/bold cyan]    {fallbacks_str}")
    if len(exc.pipe_stack) > 1:
        stack_str = " [dim]→[/dim] ".join([f"[yellow]{p}[/yellow]" for p in exc.pipe_stack])
        console.print(f"[bold cyan]Pipe Stack:[/bold cyan]   {stack_str}")
    console.print(f"\n[bold red]Error:[/bold red]        {exc}\n")
    console.print(
        f"[bold green]💡 Tip:[/bold green] Check your model configuration in [cyan].pipelex/inference/[/cyan] "
        f"or specify a different model in the [yellow]'{exc.pipe_code}'[/yellow] pipe."
    )
    console.print(f"[dim]Learn more about the inference backend system: {URLs.backend_provider_docs}[/dim]")
    console.print(f"[dim]Join our Discord for help: {URLs.discord}[/dim]\n")
    raise typer.Exit(1) from exc


def handle_model_deck_preset_error(exc: ModelDeckPresetValidatonError, context: ErrorContext) -> NoReturn:
    """Handle and display ModelDeckPresetValidatonError with formatted output.

    Args:
        exc: The model deck preset validation error exception
        context: Context for the error message
    """
    console = get_console()
    console.print(f"\n[bold red]❌ {context} failed due to model deck preset validation error[/bold red]\n")
    console.print(f"[bold cyan]Preset ID:[/bold cyan]    [yellow]'{exc.preset_id}'[/yellow]")
    console.print(f"[bold cyan]Model Type:[/bold cyan]   [yellow]'{exc.model_type}'[/yellow]")
    console.print(f"[bold cyan]Model Handle:[/bold cyan] [yellow]'{exc.model_handle}'[/yellow]")
    if exc.enabled_backends:
        backends_str = ", ".join([f"[yellow]{b}[/yellow]" for b in sorted(exc.enabled_backends)])
        console.print(f"[bold cyan]Enabled Backends:[/bold cyan] {backends_str}")
    console.print(f"\n[bold red]Error:[/bold red]        {exc.message}\n")
    backends_str = ", ".join([f"[yellow]{b}[/yellow]" for b in sorted(exc.enabled_backends)])
    console.print(
        f"[bold green]💡 Tip:[/bold green] The preset [yellow]'{exc.preset_id}'[/yellow] references model handle "
        f"[yellow]'{exc.model_handle}'[/yellow] which is not available in any enabled backend.\n"
        f"The enabled backends are: {backends_str}."
    )
    console.print(
        "[bold]Possible solutions:[/bold]\n"
        "  1. Update the preset to use a different model\n"
        f"  2. Configure model '{exc.model_handle}' in one of your enabled backends\n"
        f"  3. Enable a backend that supports [yellow]'{exc.model_handle}'[/yellow]"
    )
    console.print(f"\n[dim]Learn more about the inference backend system: {URLs.backend_provider_docs}[/dim]")
    console.print(f"[dim]Join our Discord for help: {URLs.discord}[/dim]\n")
    raise typer.Exit(1) from exc


def handle_validate_bundle_error(exc: "ValidateBundleError", bundle_path: str | None = None) -> NoReturn:
    """Handle and display ValidateBundleError with formatted output.

    Args:
        exc: The bundle validation error exception
        bundle_path: Optional path to the bundle file being validated
    """
    console = get_console()
    console.print("\n[bold red]❌ Bundle validation failed[/bold red]\n")

    if bundle_path:
        console.print(f"[bold cyan]Bundle:[/bold cyan] [yellow]{bundle_path}[/yellow]\n")
    # Display pipe validation errors
    if exc.pipe_validation_error_data:
        console.print("[bold cyan]Pipe Validation Errors:[/bold cyan]\n")
        for pipe_index, pipe_error in enumerate(exc.pipe_validation_error_data, 1):
            console.print(f"[bold yellow]{pipe_index}. {pipe_error.error_type.replace('_', ' ').title()}[/bold yellow]")

            # Display key identification info
            if pipe_error.pipe_code:
                console.print(f"   [cyan]Pipe:[/cyan] [yellow]{pipe_error.pipe_code}[/yellow]")
            if pipe_error.concept_code:
                console.print(f"   [cyan]Concept:[/cyan] [yellow]{pipe_error.concept_code}[/yellow]")
            if pipe_error.domain:
                console.print(f"   [cyan]Domain:[/cyan] [green]{pipe_error.domain}[/green]")

            # Field name if present
            if pipe_error.field_name:
                console.print(f"   [cyan]Field:[/cyan] [yellow]{pipe_error.field_name}[/yellow]")

            # Variables
            if pipe_error.variable_names:
                variables_str = ", ".join([f"[yellow]{v}[/yellow]" for v in pipe_error.variable_names])
                console.print(f"   [cyan]Variables:[/cyan] {variables_str}")

            # Error message
            console.print(f"   [cyan]→[/cyan] {pipe_error.message}")

            # Field path as secondary info
            if pipe_error.field_path:
                console.print(f"   [dim]└─ Path: {pipe_error.field_path}[/dim]")

            console.print()

    # Display dry run error message
    if exc.dry_run_error_message:
        console.print("[bold cyan]Dry Run Error:[/bold cyan]\n")
        console.print(f"[yellow]{exc.dry_run_error_message}[/yellow]\n")

    # Display helpful tips
    console.print(
        "[bold green]💡 Tip:[/bold green] Review the error messages above and check your pipeline configuration. "
        "Make sure all required fields are present and correctly formatted."
    )
    console.print(f"[dim]Learn more: {URLs.documentation}[/dim]")
    console.print(f"[dim]Join our Discord for help: {URLs.discord}[/dim]\n")
    raise typer.Exit(1) from exc
