from __future__ import annotations

import argparse
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional

from autotouch_cli.exceptions import AutotouchAPIError, AutotouchNotFoundError


@dataclass(frozen=True)
class JobCommandRuntime:
    resolve_token: Callable[[Optional[str], bool], Optional[str]]
    request_api: Callable[..., Any]
    print_json: Callable[[Any, bool], None]
    poll_job: Callable[..., Dict[str, Any]]
    api_url: Callable[[Optional[str]], str]
    default_timeout_seconds: int


def cmd_jobs_get(args: argparse.Namespace, *, runtime: JobCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    data = runtime.request_api(
        "GET",
        f"/api/bulk-jobs/{args.job_id}",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_jobs_list(args: argparse.Namespace, *, runtime: JobCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    params: Dict[str, Any] = {"limit": int(args.limit or 20)}
    if args.table_id:
        params["table_id"] = args.table_id
    if args.column_id:
        params["column_id"] = args.column_id
    if args.status:
        params["status"] = args.status
    if args.provider:
        params["provider"] = args.provider

    data = runtime.request_api(
        "GET",
        "/api/bulk-jobs",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        params=params,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_jobs_watch(args: argparse.Namespace, *, runtime: JobCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    poll_result = runtime.poll_job(
        job_id=args.job_id,
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        interval_seconds=int(args.interval or 2),
        wait_timeout_seconds=int(args.wait_timeout or 0),
        request_timeout_seconds=int(args.timeout or runtime.default_timeout_seconds),
        verbose=args.verbose,
        compact=args.compact,
        once=bool(args.once),
        print_updates=True,
    )
    final_job = poll_result.get("job") or {}
    timed_out = bool(poll_result.get("timed_out"))

    if args.once:
        runtime.print_json(final_job, args.compact)
        return

    final_summary = {
        "event": "job.timed_out" if timed_out else "job.completed",
        "job_id": final_job.get("job_id") or final_job.get("jobId") or args.job_id,
        "status": final_job.get("status"),
        "processed_rows": int(final_job.get("processed_rows") or 0),
        "error_rows": int(final_job.get("error_rows") or 0),
        "skipped_rows": int(final_job.get("skipped_rows") or 0),
        "total_rows": int(final_job.get("total_rows") or 0),
        "pending_batches": int(final_job.get("pending_batches") or 0),
        "terminal_reason": final_job.get("terminal_reason"),
        "job_status_url": f"{runtime.api_url(args.base_url)}/api/bulk-jobs/{args.job_id}",
        "timed_out": timed_out,
        "polls": int(poll_result.get("polls") or 0),
    }
    runtime.print_json(final_summary, args.compact)

    if timed_out:
        raise AutotouchNotFoundError("job watch timed out")
    status = str(final_job.get("status") or "").lower()
    if args.fail_on_error and status in {"error", "cancelled"}:
        raise AutotouchAPIError(f"job finished with status: {status}")
    if args.fail_on_partial and status == "partial":
        raise AutotouchAPIError("job finished with status: partial")


def register_jobs_subcommands(
    subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
    *,
    add_api_common_arguments: Callable[[argparse.ArgumentParser], None],
    handlers: Dict[str, Callable[[argparse.Namespace], None]],
) -> None:
    parser = subparsers.add_parser("jobs", help="Bulk job operations")
    jobs_sub = parser.add_subparsers(dest="jobs_cmd", required=True)

    get_parser = jobs_sub.add_parser("get", help="Get bulk job details")
    get_parser.add_argument("--job-id", required=True)
    add_api_common_arguments(get_parser)
    get_parser.set_defaults(func=handlers["get"])

    list_parser = jobs_sub.add_parser("list", help="List recent bulk jobs (recover latest job_id)")
    list_parser.add_argument("--table-id", help="Filter by table id")
    list_parser.add_argument("--column-id", help="Filter by column id")
    list_parser.add_argument("--status", help="Filter by status (or comma-separated statuses)")
    list_parser.add_argument("--provider", help="Filter by provider")
    list_parser.add_argument("--limit", type=int, default=20, help="Max jobs to return (1-100)")
    add_api_common_arguments(list_parser)
    list_parser.set_defaults(func=handlers["list"])

    watch_parser = jobs_sub.add_parser("watch", help="Poll bulk job status until terminal (or once)")
    watch_parser.add_argument("--job-id", required=True)
    watch_parser.add_argument("--once", action="store_true", help="Fetch once and exit")
    watch_parser.add_argument("--interval", type=int, default=2, help="Polling interval seconds")
    watch_parser.add_argument(
        "--wait-timeout",
        type=int,
        default=0,
        help="Max seconds to wait (0 = no timeout)",
    )
    watch_parser.add_argument("--fail-on-error", action="store_true", help="Exit non-zero on error/cancelled")
    watch_parser.add_argument("--fail-on-partial", action="store_true", help="Exit non-zero on partial")
    add_api_common_arguments(watch_parser)
    watch_parser.set_defaults(func=handlers["watch"])
