"""CLI commands for job cancellation."""

from __future__ import annotations

from pathlib import Path

import click

from oclawma.cli_ui import (
    accent,
    header,
    key_value,
    muted,
    print_error,
    print_info,
    print_success,
    subheader,
)
from oclawma.queue import JobQueue, JobStatus
from oclawma.queue.queue import JobQueueError

DEFAULT_QUEUE_DB = Path.home() / ".oclawma" / "queue.db"


@click.group(name="cancel")
@click.option(
    "--db-path",
    type=click.Path(),
    default=str(DEFAULT_QUEUE_DB),
    help="Path to the queue database",
    show_default=True,
)
@click.option(
    "--reason",
    "-r",
    default="User requested",
    help="Reason for cancellation",
    show_default=True,
)
@click.option(
    "--by",
    "cancelled_by",
    default=None,
    help="Who is cancelling the job (user/process name)",
)
@click.pass_context
def cancel_cli(
    ctx: click.Context,
    db_path: str,
    reason: str,
    cancelled_by: str | None,
) -> None:
    """Cancel running or pending jobs.

    Cancel a job by ID, or cancel all jobs matching filters.

    Examples:
        oclawma cancel 123                    # Cancel job 123
        oclawma cancel 123 --reason "Timeout" # Cancel with custom reason
        oclawma cancel --all-pending          # Cancel all pending jobs
        oclawma cancel --all-running          # Cancel all running jobs
    """
    ctx.ensure_object(dict)
    ctx.obj["db_path"] = db_path
    ctx.obj["reason"] = reason
    ctx.obj["cancelled_by"] = cancelled_by


@cancel_cli.command(name="job")
@click.argument("job_id", type=int)
@click.pass_context
def cancel_job(ctx: click.Context, job_id: int) -> None:
    """Cancel a specific job by ID."""
    db_path = ctx.obj["db_path"]
    reason = ctx.obj["reason"]
    cancelled_by = ctx.obj["cancelled_by"]

    try:
        with JobQueue(db_path) as queue:
            job = queue.get_job(job_id)

            if job.status not in (JobStatus.PENDING, JobStatus.RUNNING, JobStatus.PAUSED):
                print_error(f"Job {job_id} cannot be cancelled (status: {job.status.value})")
                raise click.Abort()

            result = queue.cancel(job_id, reason=reason, cancelled_by=cancelled_by)

        if result:
            print_success(f"Cancelled job {job_id}")
            click.echo(key_value("Reason", reason))
            if cancelled_by:
                click.echo(key_value("Cancelled by", cancelled_by))
        else:
            print_error(f"Failed to cancel job {job_id}")
            raise click.Abort()

    except Exception as e:
        print_error(f"Failed to cancel job: {e}")
        raise click.Abort() from e


@cancel_cli.command(name="list")
@click.option(
    "--status",
    type=click.Choice(["pending", "running", "paused", "all"], case_sensitive=False),
    default="all",
    help="Filter by job status",
    show_default=True,
)
@click.option(
    "--limit",
    "-n",
    type=int,
    default=50,
    help="Maximum number of jobs to show",
    show_default=True,
)
@click.pass_context
def cancel_list(
    ctx: click.Context,
    status: str,
    limit: int,
) -> None:
    """List jobs that can be cancelled."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            if status == "all":
                jobs = queue.get_cancellable_jobs(limit=limit)
            else:
                status_enum = JobStatus(status)
                jobs = queue.list_jobs(status=status_enum, limit=limit)

        if not jobs:
            print_info("No cancellable jobs found")
            return

        click.echo(header("CANCELLABLE JOBS", width=58))
        click.echo()

        # Group by status
        by_status: dict[str, list] = {}
        for job in jobs:
            s = job.status.value
            if s not in by_status:
                by_status[s] = []
            by_status[s].append(job)

        for status_name, status_jobs in sorted(by_status.items()):
            click.echo(subheader(status_name.upper()))
            for job in status_jobs:
                click.echo(f"  {accent(f'Job #{job.id}', bold=True)}")
                click.echo(f"    {muted('Type:')} {job.job_type}")
                click.echo(f"    {muted('Priority:')} {job.priority.name}")
                if job.started_at:
                    click.echo(f"    {muted('Started:')} {job.started_at}")
                click.echo()

    except Exception as e:
        print_error(f"Failed to list jobs: {e}")
        raise click.Abort() from e


@cancel_cli.command(name="all-pending")
@click.confirmation_option(
    prompt="Are you sure you want to cancel all pending jobs?",
    help="Confirm cancellation of all pending jobs",
)
@click.pass_context
def cancel_all_pending(ctx: click.Context) -> None:
    """Cancel all pending jobs."""
    db_path = ctx.obj["db_path"]
    reason = ctx.obj["reason"]
    cancelled_by = ctx.obj["cancelled_by"]

    try:
        with JobQueue(db_path) as queue:
            jobs = queue.list_jobs(status=JobStatus.PENDING)
            count = 0

            for job in jobs:
                result = queue.cancel(job.id, reason=reason, cancelled_by=cancelled_by)
                if result:
                    count += 1

        print_success(f"Cancelled {count} pending jobs")

    except Exception as e:
        print_error(f"Failed to cancel jobs: {e}")
        raise click.Abort() from e


@cancel_cli.command(name="all-running")
@click.confirmation_option(
    prompt="Are you sure you want to cancel all running jobs?",
    help="Confirm cancellation of all running jobs",
)
@click.pass_context
def cancel_all_running(ctx: click.Context) -> None:
    """Cancel all running jobs."""
    db_path = ctx.obj["db_path"]
    reason = ctx.obj["reason"]
    cancelled_by = ctx.obj["cancelled_by"]

    try:
        with JobQueue(db_path) as queue:
            jobs = queue.list_jobs(status=JobStatus.RUNNING)
            count = 0

            for job in jobs:
                result = queue.cancel(job.id, reason=reason, cancelled_by=cancelled_by)
                if result:
                    count += 1

        print_success(f"Cancelled {count} running jobs")

    except Exception as e:
        print_error(f"Failed to cancel jobs: {e}")
        raise click.Abort() from e


# Convenience command for direct job cancellation
@click.command(name="cancel")
@click.argument("job_id", type=int, required=False)
@click.option(
    "--db-path",
    type=click.Path(),
    default=str(DEFAULT_QUEUE_DB),
    help="Path to the queue database",
    show_default=True,
)
@click.option(
    "--reason",
    "-r",
    default="User requested",
    help="Reason for cancellation",
    show_default=True,
)
@click.option(
    "--by",
    "cancelled_by",
    default=None,
    help="Who is cancelling the job (user/process name)",
)
def cancel_command(
    job_id: int | None,
    db_path: str,
    reason: str,
    cancelled_by: str | None,
) -> None:
    """Cancel a job by ID.

    Example:
        oclawma cancel 123
        oclawma cancel 123 --reason "Timeout"
    """
    if job_id is None:
        click.echo("Usage: oclawma cancel <JOB_ID>")
        click.echo("Use 'oclawma cancel --help' for more options")
        raise click.Abort()

    try:
        with JobQueue(db_path) as queue:
            job = queue.get_job(job_id)

            if job.status not in (JobStatus.PENDING, JobStatus.RUNNING, JobStatus.PAUSED):
                print_error(f"Job {job_id} cannot be cancelled (status: {job.status.value})")
                raise click.Abort()

            result = queue.cancel(job_id, reason=reason, cancelled_by=cancelled_by)

        if result:
            print_success(f"Cancelled job {job_id}")
            click.echo(key_value("Previous status", job.status.value))
            click.echo(key_value("Reason", reason))
            if cancelled_by:
                click.echo(key_value("Cancelled by", cancelled_by))
        else:
            print_error(f"Failed to cancel job {job_id}")
            raise click.Abort()

    except JobQueueError as e:
        print_error(f"Job error: {e}")
        raise click.Abort() from e
    except Exception as e:
        print_error(f"Failed to cancel job: {e}")
        raise click.Abort() from e
