"""CLI commands for batch job operations."""

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,
    print_warning,
    subheader,
)
from oclawma.queue import BatchManager, BatchNotFoundError
from oclawma.queue.models import JobPriority
from oclawma.queue.queue import JobQueue

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


@click.group(name="batch")
@click.option(
    "--db-path",
    type=click.Path(),
    default=str(DEFAULT_QUEUE_DB),
    help="Path to the queue database",
    show_default=True,
)
@click.pass_context
def batch_cli(ctx: click.Context, db_path: str) -> None:
    """Manage batch job operations.

    Batches allow grouping related jobs together for easier management
    and bulk operations like retry, cancel, and delete.

    Examples:
        oclawma batch create --name "email-campaign"          # Create new batch
        oclawma batch submit BATCH_ID jobs.json               # Submit jobs to batch
        oclawma batch progress BATCH_ID                       # Check batch progress
        oclawma batch list                                    # List all batches
        oclawma batch retry BATCH_ID                          # Retry failed jobs
        oclawma batch cancel BATCH_ID                         # Cancel pending jobs
        oclawma batch delete BATCH_ID                         # Delete batch jobs
    """
    ctx.ensure_object(dict)
    ctx.obj["db_path"] = db_path


@batch_cli.command(name="create")
@click.option(
    "--name",
    "-n",
    type=str,
    help="Batch name for identification",
)
@click.option(
    "--description",
    "-d",
    type=str,
    help="Batch description",
)
@click.pass_context
def batch_create(ctx: click.Context, name: str | None, description: str | None) -> None:
    """Create a new batch."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            batch_id = manager.create_batch(
                name=name,
                description=description,
            )

        print_success(f"Created batch {batch_id}")
        if name:
            print_info(f"Name: {name}")

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


@batch_cli.command(name="submit")
@click.argument("batch_id")
@click.argument("payloads_file", type=click.Path(exists=True))
@click.option(
    "--priority",
    "-p",
    type=click.Choice(["critical", "high", "normal", "low"]),
    default="normal",
    help="Priority for all jobs in the batch",
)
@click.option(
    "--max-retries",
    "-r",
    type=int,
    default=3,
    help="Maximum retry attempts for each job",
)
@click.option(
    "--sequential",
    "-s",
    is_flag=True,
    help="Run jobs sequentially (with dependencies)",
)
@click.pass_context
def batch_submit(
    ctx: click.Context,
    batch_id: str,
    payloads_file: str,
    priority: str,
    max_retries: int,
    sequential: bool,
) -> None:
    """Submit jobs from a JSON file to a batch.

    The PAYLOADS_FILE should contain a JSON array of job payloads:

    \b
    [
        {"to": "user1@example.com", "subject": "Hello"},
        {"to": "user2@example.com", "subject": "Hello"}
    ]
    """
    import json

    db_path = ctx.obj["db_path"]

    # Load payloads from file
    try:
        with open(payloads_file) as f:
            payloads = json.load(f)

        if not isinstance(payloads, list):
            print_error("Payloads file must contain a JSON array")
            raise click.Abort()
    except json.JSONDecodeError as e:
        print_error(f"Invalid JSON in payloads file: {e}")
        raise click.Abort() from e
    except Exception as e:
        print_error(f"Failed to read payloads file: {e}")
        raise click.Abort() from e

    # Map priority string to enum
    priority_map = {
        "critical": JobPriority.CRITICAL,
        "high": JobPriority.HIGH,
        "normal": JobPriority.NORMAL,
        "low": JobPriority.LOW,
    }
    job_priority = priority_map[priority]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            jobs = manager.submit_jobs(
                batch_id=batch_id,
                payloads=payloads,
                priority=job_priority,
                max_retries=max_retries,
                sequential=sequential,
            )

        print_success(f"Submitted {len(jobs)} jobs to batch {batch_id}")
        if sequential:
            print_info("Jobs will run sequentially")

    except BatchNotFoundError:
        print_error(f"Batch {batch_id} not found")
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to submit jobs: {e}")
        raise click.Abort() from e


@batch_cli.command(name="progress")
@click.argument("batch_id")
@click.pass_context
def batch_progress(ctx: click.Context, batch_id: str) -> None:
    """Show batch progress."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            progress = manager.get_progress(batch_id)

        click.echo(header(f"BATCH: {batch_id[:8]}", width=58))
        click.echo()

        click.echo(subheader("PROGRESS"))
        click.echo(key_value("Status", progress.status.value.upper()))
        click.echo(key_value("Total Jobs", str(progress.total)))
        click.echo(key_value("Pending", str(progress.pending)))
        click.echo(key_value("Running", str(progress.running)))
        click.echo(key_value("Completed", str(progress.completed)))
        click.echo(key_value("Failed", str(progress.failed)))
        click.echo(key_value("Cancelled", str(progress.cancelled)))
        click.echo()

        # Progress bar
        bar_width = 40
        filled = int(bar_width * progress.percent_complete / 100)
        bar = "█" * filled + "░" * (bar_width - filled)
        click.echo(f"Progress: [{bar}] {progress.percent_complete:.1f}%")

        if progress.errors:
            click.echo()
            click.echo(subheader("ERRORS"))
            for error in progress.errors[:5]:  # Show first 5 errors
                click.echo(f"  {muted('•')} {error[:80]}")
            if len(progress.errors) > 5:
                click.echo(f"  {muted(f'... and {len(progress.errors) - 5} more')}")

    except BatchNotFoundError:
        print_error(f"Batch {batch_id} not found")
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to get batch progress: {e}")
        raise click.Abort() from e


@batch_cli.command(name="list")
@click.option(
    "--limit",
    "-n",
    type=int,
    default=20,
    help="Maximum number of batches to show",
)
@click.pass_context
def batch_list(ctx: click.Context, limit: int) -> None:
    """List all batches."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            batches = manager.list_batches()

        if not batches:
            print_info("No batches found")
            return

        # Sort by created_at (newest first)
        batches = sorted(
            batches,
            key=lambda b: b.get("created_at", ""),
            reverse=True,
        )[:limit]

        click.echo(header("BATCHES", width=58))
        click.echo()

        for batch in batches:
            batch_id = batch["batch_id"]
            name = batch.get("name", f"batch-{batch_id[:8]}")
            percent = batch.get("percent_complete", 0)

            click.echo(f"{accent(name, bold=True)} ({batch_id[:8]}...)")
            if batch.get("description"):
                click.echo(f"  {muted(batch['description'])}")

            # Mini progress bar
            bar_width = 20
            filled = int(bar_width * percent / 100)
            bar = "█" * filled + "░" * (bar_width - filled)
            job_count = batch.get("job_count", 0)
            click.echo(f"  [{bar}] {percent:.0f}% ({job_count} jobs)")
            click.echo()

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


@batch_cli.command(name="retry")
@click.argument("batch_id")
@click.option(
    "--all",
    "retry_all",
    is_flag=True,
    help="Retry all jobs, not just failed ones",
)
@click.pass_context
def batch_retry(ctx: click.Context, batch_id: str, retry_all: bool) -> None:
    """Retry failed jobs in a batch."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            result = manager.bulk_retry(
                batch_id=batch_id,
                failed_only=not retry_all,
            )

        if result.success:
            print_success(f"Retried {result.processed_count} jobs in batch {batch_id}")
        else:
            print_warning(
                f"Partial retry: {result.processed_count} retried, " f"{result.failed_count} failed"
            )
            for error in result.errors[:5]:
                print_error(f"  • {error}")

    except BatchNotFoundError:
        print_error(f"Batch {batch_id} not found")
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to retry batch: {e}")
        raise click.Abort() from e


@batch_cli.command(name="cancel")
@click.argument("batch_id")
@click.option(
    "--pending-only",
    is_flag=True,
    help="Only cancel pending jobs (not running)",
)
@click.confirmation_option(
    prompt="Are you sure you want to cancel jobs in this batch?",
    help="Confirm cancellation",
)
@click.pass_context
def batch_cancel(ctx: click.Context, batch_id: str, pending_only: bool) -> None:
    """Cancel jobs in a batch."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            result = manager.bulk_cancel(
                batch_id=batch_id,
                pending_only=pending_only,
            )

        if result.success:
            print_success(f"Cancelled {result.processed_count} jobs in batch {batch_id}")
        else:
            print_warning(
                f"Partial cancellation: {result.processed_count} cancelled, "
                f"{result.failed_count} failed"
            )

    except BatchNotFoundError:
        print_error(f"Batch {batch_id} not found")
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to cancel batch: {e}")
        raise click.Abort() from e


@batch_cli.command(name="delete")
@click.argument("batch_id")
@click.option(
    "--completed-only",
    is_flag=True,
    help="Only delete completed jobs",
)
@click.option(
    "--force",
    is_flag=True,
    help="Delete all jobs regardless of status",
)
@click.confirmation_option(
    prompt="Are you sure you want to delete jobs in this batch?",
    help="Confirm deletion",
)
@click.pass_context
def batch_delete(ctx: click.Context, batch_id: str, completed_only: bool, force: bool) -> None:
    """Delete jobs in a batch."""
    db_path = ctx.obj["db_path"]

    if force:
        completed_only = False

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            result = manager.bulk_delete(
                batch_id=batch_id,
                completed_only=completed_only,
            )

        if result.success:
            print_success(f"Deleted {result.processed_count} jobs from batch {batch_id}")
        else:
            print_warning(
                f"Partial deletion: {result.processed_count} deleted, "
                f"{result.failed_count} failed"
            )

    except BatchNotFoundError:
        print_error(f"Batch {batch_id} not found")
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to delete batch: {e}")
        raise click.Abort() from e


@batch_cli.command(name="stats")
@click.argument("batch_id")
@click.pass_context
def batch_stats(ctx: click.Context, batch_id: str) -> None:
    """Show detailed statistics for a batch."""
    db_path = ctx.obj["db_path"]

    try:
        with JobQueue(db_path) as queue:
            manager = BatchManager(queue)
            stats = manager.get_batch_stats(batch_id)

        click.echo(header(f"BATCH STATS: {batch_id[:8]}", width=58))
        click.echo()

        click.echo(subheader("STATUS"))
        click.echo(key_value("Status", stats["status"].upper()))
        click.echo(key_value("Total Jobs", str(stats["total_jobs"])))
        click.echo(key_value("Pending", str(stats["pending"])))
        click.echo(key_value("Running", str(stats["running"])))
        click.echo(key_value("Completed", str(stats["completed"])))
        click.echo(key_value("Failed", str(stats["failed"])))
        click.echo(key_value("Cancelled", str(stats["cancelled"])))
        click.echo()

        click.echo(subheader("PERFORMANCE"))
        click.echo(key_value("Percent Complete", f"{stats['percent_complete']:.1f}%"))
        click.echo(key_value("Average Retries", f"{stats['average_retries']:.2f}"))
        click.echo(key_value("Avg Processing Time", f"{stats['average_processing_time']:.2f}s"))
        click.echo(key_value("Total Processing Time", f"{stats['total_processing_time']:.2f}s"))

        if stats["errors"]:
            click.echo()
            click.echo(subheader("ERRORS"))
            for error in stats["errors"][:10]:
                click.echo(f"  {muted('•')} {error[:80]}")

    except BatchNotFoundError:
        print_error(f"Batch {batch_id} not found")
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to get batch stats: {e}")
        raise click.Abort() from e
