"""Discover / backfill related, upsell, and cross-sell master associations.

CLI:
    python -m app.jobs.discover_product_associations --dry-run
    python -m app.jobs.discover_product_associations --batch-size 250
    python -m app.jobs.discover_product_associations --offset 0 --limit 500
    python -m app.jobs.discover_product_associations --skus ACH-B12,ACH-W3015 --cap 5
"""

from __future__ import annotations

import argparse
import json
import logging
import sys
from typing import List, Optional

logger = logging.getLogger(__name__)


def run_discover_product_associations(
    *,
    skus: Optional[List[str]] = None,
    cap: int = 5,
    dry_run: bool = False,
    bidirectional: bool = True,
    batch_size: int = 250,
    offset: int = 0,
    limit: Optional[int] = None,
    max_batches: Optional[int] = None,
) -> dict:
    from db.master_associations import discover_associations, discover_associations_in_batches
    from db.session import get_session

    def _on_progress(info: dict) -> None:
        total = info.get("total_candidates") or "?"
        done = info.get("sources_done") or 0
        batch_no = info.get("batch") or "?"
        logger.info(
            "association discovery batch %s: offset=%s sources=%s peers=%s auto_rows=%s (%s/%s done)",
            batch_no,
            info.get("offset"),
            info.get("sources"),
            info.get("peer_sources"),
            info.get("auto_rows"),
            done,
            total,
        )

    with get_session() as session:
        # Single-slice mode when --limit is set without multi-batch intent.
        if limit is not None and max_batches is None:
            result = discover_associations(
                session,
                skus=skus,
                cap=cap,
                dry_run=dry_run,
                bidirectional=bidirectional,
                source_label="association_backfill",
                offset=offset,
                limit=limit,
            )
            if not dry_run:
                session.commit()
            return result

        result = discover_associations_in_batches(
            session,
            skus=skus,
            cap=cap,
            dry_run=dry_run,
            bidirectional=bidirectional,
            source_label="association_backfill",
            batch_size=batch_size,
            offset=offset,
            max_batches=max_batches,
            commit=not dry_run,
            progress_callback=_on_progress,
        )
    return result


def _parse_sku_list(raw: Optional[str]) -> List[str]:
    if not raw:
        return []
    return [part.strip() for part in raw.split(",") if part.strip()]


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Discover related / upsell / cross-sell associations for master SKUs"
    )
    parser.add_argument("--dry-run", action="store_true", help="Preview without writing")
    parser.add_argument(
        "--skus",
        default=None,
        help="Optional comma-separated SKU filter (default: all active master SKUs)",
    )
    parser.add_argument(
        "--cap",
        type=int,
        default=5,
        help="Max links per type (clamped to 4-6, default 5)",
    )
    parser.add_argument(
        "--no-bidirectional",
        action="store_true",
        help="Only write outbound links for the filtered SKUs (skip peer reverse updates)",
    )
    parser.add_argument(
        "--batch-size",
        type=int,
        default=250,
        help="SKUs per batch when processing the full catalog (default 250)",
    )
    parser.add_argument(
        "--offset",
        type=int,
        default=0,
        help="Skip this many SKUs in the sorted active list (resume support)",
    )
    parser.add_argument(
        "--limit",
        type=int,
        default=None,
        help="Process only this many SKUs starting at --offset (single slice)",
    )
    parser.add_argument(
        "--max-batches",
        type=int,
        default=None,
        help="Stop after N batches (useful for staged runs)",
    )
    args = parser.parse_args()

    result = run_discover_product_associations(
        skus=_parse_sku_list(args.skus) or None,
        cap=args.cap,
        dry_run=args.dry_run,
        bidirectional=not args.no_bidirectional,
        batch_size=args.batch_size,
        offset=args.offset,
        limit=args.limit,
        max_batches=args.max_batches,
    )
    if result.get("status") != "ok":
        print(result.get("error", "unknown error"), file=sys.stderr)
        return 1
    # Keep stdout readable for large runs.
    printable = dict(result)
    batches = printable.get("batches")
    if isinstance(batches, list) and len(batches) > 20:
        printable["batches"] = batches[:5] + [{"...": f"{len(batches) - 10} more"}] + batches[-5:]
    print(json.dumps(printable, indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
    )
    sys.exit(main())
