"""Orchestrate every seed command in dependency order.

Single canonical entry point. Replaces the hardcoded list in ``docker-compose.yml``
and gives ``make reset-db`` something to call.

Usage:
    python manage.py seed_all                    # demo volume (default)
    python manage.py seed_all --volume heavy     # performance-test volume
    python manage.py seed_all --only repository alumni
    python manage.py seed_all --skip rep_live    # skip BBB-related seed
"""

from __future__ import annotations

import time

from django.core.management import call_command
from django.core.management.base import BaseCommand

from apps.core.seeders.volumes import RECORD_COUNTS


# (label, command, kwargs_factory)
#
# kwargs_factory takes the volume tier dict and returns the kwargs to call
# the management command with. Each step is best-effort: if a sub-seeder
# raises we log and continue so a partial seed still leaves the rest of the
# demo populated.
STEPS: list[tuple[str, str, callable]] = [
    ("Roles / Groups", "seed_roles", lambda v: {}),
    ("Notification templates", "seed_notification_templates", lambda v: {}),
    ("Core institution + superuser", "seed_core", lambda v: {}),
    ("User pool (Faker)", "seed_users", lambda v: {"volume": v["_label"]}),
    ("Repository documents", "seed_repository", lambda v: {
        "documents": v["documents"],
        "events": v["events_per_doc"],
    }),
    ("M&EL framework", "seed_mel", lambda v: {"volume": v["_label"]}),
    ("RIMS lifecycle", "seed_rims", lambda v: {"volume": v["_label"]}),
    ("REP courses + assessments", "seed_rep", lambda v: {"volume": v["_label"]}),
    ("REP glossary", "seed_rep_glossary", lambda v: {"volume": v["_label"]}),
    ("REP wiki", "seed_rep_wiki", lambda v: {"volume": v["_label"]}),
    ("REP surveys", "seed_rep_surveys", lambda v: {"volume": v["_label"]}),
    ("REP live sessions", "seed_rep_live", lambda v: {"volume": v["_label"]}),
    ("REP networking", "seed_rep_networking", lambda v: {"volume": v["_label"]}),
    ("REP personalisation", "seed_rep_personalisation", lambda v: {"volume": v["_label"]}),
    ("Alumni", "seed_alumni", lambda v: {"volume": v["_label"]}),
    ("Alumni recognition", "seed_alumni_recognition", lambda v: {"volume": v["_label"]}),
    ("SME-Hub SP1–SP5", "seed_smehub", lambda v: {"volume": v["_label"]}),
    ("Assistant history", "seed_assistant", lambda v: {"volume": v["_label"]}),
    ("Audit log replay", "seed_audit", lambda v: {"volume": v["_label"]}),
]


class Command(BaseCommand):
    help = "Run every seed_* command in dependency order at the chosen volume tier."

    def add_arguments(self, parser):
        parser.add_argument(
            "--volume",
            choices=("minimal", "demo", "heavy"),
            default="demo",
            help="Per-tier record counts (see apps/core/seeders/volumes.py). Default: demo.",
        )
        parser.add_argument(
            "--only",
            nargs="*",
            default=None,
            help="Run only steps whose label contains one of these (case-insensitive).",
        )
        parser.add_argument(
            "--skip",
            nargs="*",
            default=[],
            help="Skip steps whose label contains one of these (case-insensitive).",
        )
        parser.add_argument(
            "--continue-on-error",
            action="store_true",
            default=True,
            help="Continue with subsequent steps if a step raises (default: True).",
        )
        parser.add_argument(
            "--fail-fast",
            dest="continue_on_error",
            action="store_false",
            help="Abort on first failing step.",
        )

    def handle(self, *args, **opts):
        tier = opts["volume"]
        v = {**RECORD_COUNTS[tier], "_label": tier}
        only = opts.get("only") or None
        skip = opts.get("skip") or []
        continue_on_error = opts["continue_on_error"]

        total_start = time.time()
        ran = []
        skipped = []
        failed = []

        for label, command, factory in STEPS:
            if only and not any(token.lower() in label.lower() for token in only):
                skipped.append(label)
                continue
            if any(token.lower() in label.lower() for token in skip):
                skipped.append(label)
                continue

            self.stdout.write(self.style.HTTP_INFO(f"\n=== {label} ({command}) ==="))
            kwargs = factory(v)
            step_start = time.time()
            try:
                call_command(command, **kwargs)
            except Exception as exc:  # noqa: BLE001 — orchestrator must be resilient
                failed.append((label, command, str(exc)))
                self.stdout.write(self.style.ERROR(f"  FAILED: {exc!r}"))
                if not continue_on_error:
                    raise
                continue
            ran.append(label)
            self.stdout.write(self.style.SUCCESS(f"  done in {time.time() - step_start:.1f}s"))

        total = time.time() - total_start
        self.stdout.write("")
        self.stdout.write(self.style.MIGRATE_HEADING(
            f"seed_all complete in {total:.1f}s "
            f"(tier={tier}; ran={len(ran)} skipped={len(skipped)} failed={len(failed)})"
        ))
        if failed:
            self.stdout.write(self.style.ERROR("Failures:"))
            for label, cmd, err in failed:
                self.stdout.write(self.style.ERROR(f"  - {label} [{cmd}]: {err}"))
