from collections.abc import Callable

from django.db.models import Q

from printing.models import PrintingJob


def extract_request_name(job: PrintingJob) -> str:
    raw = job.external_raw or {}
    if isinstance(raw, dict):
        request_name = str(raw.get("YanSe") or "").strip()
        if request_name:
            return request_name
    return str(job.external_product_name or "").strip()


def run_external_product_image_backfill(
    *,
    fetch_image: Callable[[str], dict],
    upload_image: Callable[[object, dict], None],
    merchant_id: int | None = None,
    product_id: int | None = None,
    limit: int | None = None,
    dry_run: bool = False,
    on_success: Callable[[str], None] | None = None,
    on_error: Callable[[str], None] | None = None,
    on_info: Callable[[str], None] | None = None,
) -> dict:
    queryset = (
        PrintingJob.objects.filter(
            printing_order__external_order_id__isnull=False,
        )
        .exclude(printing_order__external_order_id="")
        .filter(Q(product__image__isnull=True) | Q(product__image=""))
        .select_related("product", "printing_order", "product__merchant")
        .order_by("-id")
    )

    if merchant_id:
        queryset = queryset.filter(product__merchant_id=merchant_id)

    if product_id:
        queryset = queryset.filter(product_id=product_id)

    if limit is not None:
        limit = int(limit)
        if limit <= 0:
            raise ValueError("limit must be greater than 0")

    success_count = 0
    failed_count = 0
    skipped_with_image_count = 0
    skipped_without_request_name_count = 0
    skipped_duplicate_product_count = 0
    selected_count = 0
    scanned_count = 0
    seen_product_ids = set()

    for job in queryset.iterator(chunk_size=1000):
        scanned_count += 1
        product = job.product
        if product.image:
            skipped_with_image_count += 1
            continue

        if product.id in seen_product_ids:
            skipped_duplicate_product_count += 1
            continue

        request_name = extract_request_name(job)
        if not request_name:
            skipped_without_request_name_count += 1
            continue

        seen_product_ids.add(product.id)
        selected_count += 1

        if dry_run:
            if on_info:
                on_info(
                    "[DRY-RUN] "
                    f"job_id={job.id} "
                    f"product_id={product.id} "
                    f"merchant_id={product.merchant_id} "
                    f"product_name={product.name} "
                    f"request_name={request_name}"
                )
        else:
            try:
                image_payload = fetch_image(request_name)
                upload_image(product, image_payload)
            except Exception as exc:
                failed_count += 1
                if on_error:
                    on_error(
                        "补图失败: "
                        f"job_id={job.id} "
                        f"product_id={product.id} "
                        f"merchant_id={product.merchant_id} "
                        f"product_name={product.name} "
                        f"request_name={request_name} "
                        f"error={exc}"
                    )
            else:
                success_count += 1
                if on_success:
                    on_success(
                        "补图成功: "
                        f"job_id={job.id} "
                        f"product_id={product.id} "
                        f"merchant_id={product.merchant_id} "
                        f"product_name={product.name} "
                        f"request_name={request_name}"
                    )

        if limit is not None and selected_count >= limit:
            break

    payload = {
        "scanned": scanned_count,
        "selected": selected_count,
        "success": success_count,
        "failed": failed_count,
        "skipped_with_image": skipped_with_image_count,
        "skipped_without_request_name": skipped_without_request_name_count,
        "skipped_duplicate_product": skipped_duplicate_product_count,
        "merchant_id": merchant_id,
        "product_id": product_id,
        "limit": limit,
        "dry_run": dry_run,
    }

    if dry_run and on_info:
        on_info("dry-run 完成")

    return payload
