"""One-off backfill of ``Library.managed_by`` for pre-existing libraries.

``managed_by`` is stamped from the creating API token's name, so
libraries created before the property existed have it null. This
command labels them:

* Workspace-scoped libraries get :func:`infer_legacy_manager`'s answer —
  ``Kairos`` for ``kairos-mail-*`` workspace ids, ``Daedalus`` otherwise.
* Global libraries that Spelunker ingested into (any ``IngestJob`` with
  ``source="spelunker"``) get the Spelunker label.
* Everything else stays null (hand-made in the web UI).

Label flags let the operator match the *actual* production token names
so backfilled rows render identically to newly stamped ones.

Idempotent: only null ``managed_by`` is ever written, and the lazy fill
in ``workspace_create`` is also null-only, so re-runs and later API
traffic never overwrite these labels.
"""

from __future__ import annotations

import logging

from django.core.management.base import BaseCommand, CommandError

from library.models import IngestJob

logger = logging.getLogger(__name__)


class Command(BaseCommand):
    help = (
        "Backfill Library.managed_by for libraries created before the "
        "property existed. Workspace libraries are labelled by inference "
        "(kairos-mail-* → Kairos, else Daedalus); global libraries with "
        "Spelunker ingest jobs get the Spelunker label."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--daedalus-label", default="Daedalus",
            help="Label for non-Kairos workspace libraries (default: Daedalus).",
        )
        parser.add_argument(
            "--kairos-label", default="Kairos",
            help="Label for kairos-mail-* workspace libraries (default: Kairos).",
        )
        parser.add_argument(
            "--spelunker-label", default="Spelunker",
            help="Label for global libraries with Spelunker ingest jobs "
                 "(default: Spelunker).",
        )
        parser.add_argument(
            "--dry-run", action="store_true",
            help="Report what would be labelled, don't persist.",
        )

    def handle(self, *args, **options):
        try:
            from library.models import Library, infer_legacy_manager
        except Exception as exc:  # pragma: no cover
            raise CommandError(
                f"Could not import library.models.Library (Neo4j unreachable?): {exc}"
            ) from exc

        overrides = {
            "Daedalus": options["daedalus_label"],
            "Kairos": options["kairos_label"],
        }

        spelunker_uids = set(
            IngestJob.objects
            .filter(source="spelunker")
            .values_list("library_uid", flat=True)
            .distinct()
        )

        candidates = list(Library.nodes.filter(managed_by__isnull=True))

        to_label = []
        for lib in candidates:
            label = infer_legacy_manager(lib.workspace_id)
            if label:
                label = overrides[label]
            elif lib.uid in spelunker_uids:
                label = options["spelunker_label"]
            if label:
                to_label.append((lib, label))

        self.stdout.write(f"Libraries with null managed_by: {len(candidates)}")
        self.stdout.write(
            self.style.SUCCESS(f"Will label: {len(to_label)} "
                               f"(unmanaged, left null: {len(candidates) - len(to_label)})")
        )

        for lib, label in to_label:
            self.stdout.write(f"  {lib.uid}  {lib.name!r} → {label}")

        if options["dry_run"]:
            self.stdout.write(self.style.WARNING("--dry-run: nothing written."))
            return

        for lib, label in to_label:
            lib.managed_by = label
            lib.save()
            logger.info(
                "backfill_managed_by uid=%s name=%s label=%s",
                lib.uid, lib.name, label,
            )
        self.stdout.write(self.style.SUCCESS("Done."))
