from django.core.management.base import BaseCommand
from django.db import transaction
from sbas.models import Question
import re
from textwrap import shorten

UNWRAP_FIELDS = [
    "stem",
    "feedback",
    "a_answer",
    "a_feedback",
    "b_answer",
    "b_feedback",
    "c_answer",
    "c_feedback",
    "d_answer",
    "d_feedback",
    "e_answer",
    "e_feedback",
]

RE_SINGLE_P = re.compile(r"\s*<p\b[^>]*>(.*)</p>\s*$", flags=re.IGNORECASE | re.DOTALL)


def unwrap_single_p(html: str) -> str:
    if not html or not isinstance(html, str):
        return html
    m = RE_SINGLE_P.fullmatch(html)
    return m.group(1) if m else html


class Command(BaseCommand):
    help = "Normalize Question HTML fields by unwrapping a single root <p>...</p>. Safe: supports --dry-run and batching."

    def add_arguments(self, parser):
        parser.add_argument("--commit", action="store_true", help="Apply changes; without this flag it's a dry-run.")
        parser.add_argument("--batch-size", type=int, default=200, help="How many objects to fetch per loop.")
        parser.add_argument("--limit", type=int, default=0, help="Limit number of questions processed (0 = all).")
        parser.add_argument("--verbose", action="store_true", help="Show full before/after for each change.")
        parser.add_argument("--start-id", type=int, default=0, help="Start from Question.id >= start-id (useful for resuming).")

    def handle(self, *args, **opts):
        commit = opts["commit"]
        batch_size = opts["batch_size"]
        limit = opts["limit"] or None
        verbose = opts["verbose"]
        start_id = opts["start_id"]

        qs = Question.objects.all().order_by("id")
        if start_id:
            qs = qs.filter(id__gte=start_id)
        total = qs.count()
        self.stdout.write(f"Found {total} questions to inspect (start_id={start_id}).")

        processed = 0
        changed = 0

        it = qs.iterator(chunk_size=batch_size)
        for q in it:
            processed += 1
            if limit and processed > limit:
                break

            changed_any = False
            updates = {}
            for f in UNWRAP_FIELDS:
                orig = getattr(q, f, None)
                new = unwrap_single_p(orig)
                if new is not None and new != orig:
                    changed_any = True
                    updates[f] = new

            if changed_any:
                changed += 1
                if verbose:
                    self.stdout.write(f"Question id={q.id} will change fields: {', '.join(updates.keys())}")
                    for f, newval in updates.items():
                        oldval = getattr(q, f)
                        self.stdout.write("  Field: %s\n    BEFORE: %s\n    AFTER : %s\n" % (
                            f,
                            (oldval or "")[:400],
                            (newval or "")[:400],
                        ))
                else:
                    sample = ", ".join(f"{k}({shorten((getattr(q,k) or ''), width=60)})" for k in updates.keys())
                    self.stdout.write(f"[DRY] id={q.id} -> {sample}" if not commit else f"[APPLY] id={q.id} -> {', '.join(updates.keys())}")

                if commit:
                    for k, v in updates.items():
                        setattr(q, k, v)
                    try:
                        with transaction.atomic():
                            q.save(update_fields=list(updates.keys()))
                    except Exception as e:
                        self.stderr.write(f"Failed to save Question id={q.id}: {e}")

            if processed % max(1, batch_size) == 0:
                self.stdout.write(f"Processed {processed} questions...")

        self.stdout.write(f"Done. Processed {processed} questions, {changed} would change{' (committed)' if commit else ' (dry-run)'}.")
