import signal
import time

import anyio
import click
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from sayer import Sayer

import asyncmq
from asyncmq import __version__  # noqa
from asyncmq.cli.utils import (
    WORKERS_LOGO,
    get_centered_logo,
    get_print_banner,
    print_worker_banner,
    run_cmd,
)
from asyncmq.logging import logger

console = Console()

worker_cli = Sayer(
    name="worker",
    help="Manages AsyncMQ worker processes.",
    invoke_without_command=True,
)


@worker_cli.callback(invoke_without_command=True)
def _worker_callback(ctx: click.Context) -> None:
    """
    Manages AsyncMQ worker processes.

    This is the main command group for worker-related actions. If no subcommand
    is provided, it prints the help message for the worker commands.

    Args:
        ctx: The Click context object, passed automatically by Click.
    """
    tokens = getattr(ctx, "protected_args", None)
    if tokens is None:
        tokens = ctx.args
    if tokens and tokens[0] in ctx.command.commands:
        return

    # If no subcommand, print custom worker help and the standard Click help.
    _print_worker_help()
    click.echo(ctx.get_help())


def _print_worker_help() -> None:
    """
    Prints a custom help message for the worker command group.

    Displays the AsyncMQ logo, a header for worker commands, a brief description,
    and examples of how to use the worker start command. The help message is
    formatted within a Rich Panel.
    """
    text = Text()  # Create a Rich Text object to build the formatted output.
    # Add the centered AsyncMQ logo with bold cyan styling.
    text.append(get_centered_logo(), style="bold cyan")
    # Add a header for worker commands with bold cyan styling.
    text.append("⚙️  Worker Commands\n\n", style="bold cyan")
    # Add a descriptive sentence about worker management.
    text.append("Manage AsyncMQ workers to process jobs.\n\n", style="white")
    # Add a section header for examples with bold yellow styling.
    text.append("Examples:\n", style="bold yellow")
    # Add example commands.
    text.append("  asyncmq worker start myqueue --concurrency 2\n")
    text.append("  asyncmq worker start myqueue --concurrency 5\n")
    # Print the text within a Rich Panel with a specific title and border style.
    console.print(Panel(text, title="Worker CLI", border_style="cyan"))


@click.command("start")
@click.argument("queue")
@click.option("--concurrency", required=False, help="Number of concurrent workers.")
def start_worker(queue: str, concurrency: int | str | None = None) -> None:
    """
    Starts an AsyncMQ worker process for a specified queue.

    This command initializes and runs the worker, which listens to the given
    queue and processes messages. It prints a banner with worker details before
    starting. The worker can be stopped by pressing Ctrl+C.

    Args:
        queue: The name of the message queue to listen to. This is a required
               command-line argument.
        concurrency: The number of worker instances to run concurrently.
                     Defaults to 1.
    """
    from asyncmq.runners import start_worker

    # Ensure the queue name is not empty.
    if not queue:
        raise click.UsageError("Queue name cannot be empty")

    concurrency = concurrency or asyncmq.monkay.settings.worker_concurrency
    if isinstance(concurrency, str):
        concurrency = int(concurrency)

    # Print the worker banner with configuration details.
    print_worker_banner(queue, concurrency, asyncmq.monkay.settings.backend.__class__.__name__, __version__)
    logger_level = getattr(asyncmq.monkay.settings, "logging_level", "info")
    log = getattr(logger, logger_level.lower())
    try:
        # Start the worker using anyio's run function.
        # The lambda function wraps the start_worker call to be compatible with anyio.run.
        log(f"Starting worker for queue '{queue}' with concurrency {concurrency}")
        run_cmd(lambda: start_worker(queue_name=queue, concurrency=concurrency))
    except KeyboardInterrupt:
        console.print("\n[bold yellow]Worker shutting down gracefully...[/bold yellow]")
    except anyio.get_cancelled_exc_class():
        # Handle KeyboardInterrupt (Ctrl+C) for graceful shutdown.
        console.print("\n[bold yellow]Worker shutting down gracefully...[/bold yellow]")
    except Exception as e:
        # Catch any other exceptions during worker execution.
        console.print(f"[red]Worker crashed: {e}[/red]")
        # Abort the Click process with an error.
        raise click.Abort() from e


async def signal_handler(scope: anyio.CancelScope) -> None:
    """Listens for signals and cancels the task group."""
    with anyio.open_signal_receiver(signal.SIGINT, signal.SIGTERM) as signals:
        async for signum in signals:
            if signum == signal.SIGINT:
                console.print("\n[yellow]KeyboardInterrupt received (Ctrl+C).[/yellow]")
            else:
                console.print(f"\n[yellow]Received signal {signum}.[/yellow]")
            scope.cancel()  # Cancel the task group to initiate shutdown
            return  # Exit the signal handler task


@click.command("list")
def list_workers() -> None:
    """
    List all currently registered workers.

    Retrieves the list of workers from the backend and displays them
    in a table, including their ID, queue, concurrency, and last heartbeat timestamp.
    """
    get_print_banner(WORKERS_LOGO, title="AsyncMQ List Workers")
    backend = asyncmq.monkay.settings.backend
    workers = run_cmd(backend.list_workers) or []
    table = Table(title="Workers")
    table.add_column("Worker ID", style="green")
    table.add_column("Queue", style="magenta")
    table.add_column("Concurrency", justify="right")
    table.add_column("Last Heartbeat", style="yellow")
    for w in workers:
        heartbeat_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(w.heartbeat))
        table.add_row(w.id, w.queue, str(w.concurrency), heartbeat_str)
    console.print(table)


@click.command("register")
@click.argument("worker_id")
@click.argument("queue")
@click.option("--concurrency", default=1, help="Concurrency level for the worker.")
def register_worker(worker_id: str, queue: str, concurrency: int) -> None:
    """
    Register or update a worker's heartbeat in the backend.

    This command registers a worker with a specific ID to a given queue
    and sets its concurrency level. It updates the worker's timestamp
    in the backend to indicate it is active.

    Args:
        worker_id: The unique identifier for the worker.
        queue: The name of the queue the worker will process tasks from.
        concurrency: The maximum number of tasks the worker can process concurrently.
                     Defaults to 1.
    """
    get_print_banner(WORKERS_LOGO, title="AsyncMQ Register Workers")
    backend = asyncmq.monkay.settings.backend
    timestamp = time.time()
    run_cmd(
        lambda: backend.register_worker(
            worker_id=worker_id,
            queue=queue,
            concurrency=concurrency,
            timestamp=timestamp,
        )
    )
    console.print(
        f":white_check_mark: Worker [bold]{worker_id}[/] registered on queue [bold]{queue}[/] with concurrency {concurrency}."
    )


@click.command("deregister")
@click.argument("worker_id")
def deregister_worker(worker_id: str) -> None:
    """
    Deregister a worker from the backend.

    This command removes a worker's entry from the backend, effectively
    deregistering it.

    Args:
        worker_id: The unique identifier of the worker to deregister.
    """
    get_print_banner(WORKERS_LOGO, title="AsyncMQ Deregister Workers")
    backend = asyncmq.monkay.settings.backend
    run_cmd(lambda: backend.deregister_worker(worker_id))
    console.print(f":white_check_mark: Worker [bold]{worker_id}[/] deregistered.")


worker_cli.add_command(start_worker)
worker_cli.add_command(list_workers)
worker_cli.add_command(register_worker)
worker_cli.add_command(deregister_worker)
worker_app = worker_cli.cli
