"""Phase 4.5 backfill — populate the cross-module participant timeline.

Walks the legacy data we already track per-domain and mirrors it into the
generic :class:`Participant` / :class:`TrackingRecord` / :class:`TrackingMilestone`
schema. Idempotent — safe to re-run because every insert uses
``get_or_create`` keyed on the natural identity of the upstream row.

Sources read:
* ``SMEHubTrackingRecord`` rows — entrepreneurs + sp1..sp5 milestone buckets.
* ``UpstreamEvent`` rows that carry a ``subject`` (User) — mirror to the
  corresponding source_module timeline.
* ``alumni_profiles.AlumniProfile`` rows — link Participants to alumni profile.
"""
from __future__ import annotations

from django.db import migrations
from django.utils import timezone


_MODULE_BY_UPSTREAM = {
    "rims": "rims",
    "rep": "rep",
    "repository": "repository",
    "smehub": "smehub",
    "alumni": "alumni",
}


def _ensure_participant(Participant, user_id, *, personas, primary):
    participant, created = Participant.objects.get_or_create(
        user_id=user_id,
        defaults={"personas": list(personas), "primary_persona": primary},
    )
    changed = False
    existing = list(participant.personas or [])
    for p in personas:
        if p not in existing:
            existing.append(p)
            changed = True
    if changed:
        participant.personas = existing
    if not participant.primary_persona and primary:
        participant.primary_persona = primary
        changed = True
    if changed:
        participant.save(update_fields=["personas", "primary_persona", "updated_at"])
    return participant


def _ensure_record(TrackingRecord, *, participant, source_module):
    record, _ = TrackingRecord.objects.get_or_create(
        participant=participant,
        source_module=source_module,
    )
    return record


def _create_milestone(TrackingMilestone, *, record, event_type, source_id, occurred_at, payload, label, source_module):
    obj, created = TrackingMilestone.objects.get_or_create(
        record=record,
        event_type=event_type,
        source_id=str(source_id),
        defaults={
            "occurred_at": occurred_at,
            "source_module": source_module,
            "payload": payload or {},
            "label": label,
        },
    )
    return obj, created


def backfill(apps, schema_editor):
    Participant = apps.get_model("mel_tracking", "Participant")
    TrackingRecord = apps.get_model("mel_tracking", "TrackingRecord")
    TrackingMilestone = apps.get_model("mel_tracking", "TrackingMilestone")
    SMEHubTrackingRecord = apps.get_model("mel_tracking", "SMEHubTrackingRecord")
    UpstreamEvent = apps.get_model("mel_tracking", "UpstreamEvent")

    try:
        AlumniProfile = apps.get_model("alumni_profiles", "AlumniProfile")
    except LookupError:
        AlumniProfile = None

    # ---- SME-Hub ----
    sme_records = (
        SMEHubTrackingRecord.objects.all()
        .select_related("entrepreneur", "business", "baseline_snapshot")
    )
    bucket_to_module = "smehub"
    for sme in sme_records.iterator():
        ent = sme.entrepreneur
        if ent is None or ent.user_id is None:
            continue
        participant = _ensure_participant(
            Participant,
            ent.user_id,
            personas=["entrepreneur"],
            primary="entrepreneur",
        )
        if participant.entrepreneur_profile_id != ent.pk:
            participant.entrepreneur_profile_id = ent.pk
            participant.save(update_fields=["entrepreneur_profile", "updated_at"])
        record = _ensure_record(
            TrackingRecord,
            participant=participant,
            source_module=bucket_to_module,
        )
        # Baseline event.
        if sme.sp1_baseline_at:
            _create_milestone(
                TrackingMilestone,
                record=record,
                event_type="smehub_baseline_initialised",
                source_id=f"baseline:{sme.pk}",
                occurred_at=sme.sp1_baseline_at,
                payload={"business_id": sme.business_id, "baseline_id": sme.baseline_snapshot_id},
                label="SME-Hub baseline initialised",
                source_module=bucket_to_module,
            )
        # SP2..SP5 buckets.
        for bucket in ("sp2_milestones", "sp3_partnerships", "sp4_commercialisation", "sp5_investment"):
            for entry in (getattr(sme, bucket, None) or []):
                key = entry.get("event") or "milestone"
                payload = entry.get("payload") or {}
                ts_iso = entry.get("ts")
                try:
                    occurred = timezone.datetime.fromisoformat(ts_iso) if ts_iso else timezone.now()
                except (TypeError, ValueError):
                    occurred = timezone.now()
                source_id = f"{bucket}:{key}:{payload.get('id') or ts_iso}"
                _create_milestone(
                    TrackingMilestone,
                    record=record,
                    event_type=f"smehub_{key}",
                    source_id=source_id,
                    occurred_at=occurred,
                    payload={"bucket": bucket, **payload},
                    label=key.replace("_", " ").title(),
                    source_module=bucket_to_module,
                )
        # Refresh last_event_at on the record.
        latest = (
            TrackingMilestone.objects.filter(record=record)
            .order_by("-occurred_at")
            .values_list("occurred_at", flat=True)
            .first()
        )
        if latest and (record.last_event_at is None or latest > record.last_event_at):
            record.last_event_at = latest
            record.save(update_fields=["last_event_at", "updated_at"])

    # ---- UpstreamEvent (RIMS / REP / Repository / Alumni when subject present) ----
    for ue in UpstreamEvent.objects.exclude(subject__isnull=True).iterator():
        module = _MODULE_BY_UPSTREAM.get(ue.module, ue.module)
        if module not in {"rims", "rep", "repository", "smehub", "alumni"}:
            continue
        persona_map = {
            "rims": "scholar",
            "rep": "learner",
            "repository": "researcher",
            "smehub": "entrepreneur",
            "alumni": "alumni",
        }
        primary = persona_map.get(module)
        participant = _ensure_participant(
            Participant,
            ue.subject_id,
            personas=[primary] if primary else [],
            primary=primary or "",
        )
        record = _ensure_record(
            TrackingRecord,
            participant=participant,
            source_module=module,
        )
        _create_milestone(
            TrackingMilestone,
            record=record,
            event_type=ue.event_type,
            source_id=ue.source_object_id,
            occurred_at=ue.recorded_at,
            payload=ue.payload or {},
            label=ue.event_type.replace("_", " ").title(),
            source_module=module,
        )
        latest = ue.recorded_at
        if record.last_event_at is None or latest > record.last_event_at:
            record.last_event_at = latest
            record.save(update_fields=["last_event_at", "updated_at"])

    # ---- Link Alumni profiles to participants. ----
    if AlumniProfile is not None:
        for ap in AlumniProfile.objects.iterator():
            if ap.user_id is None:
                continue
            participant = _ensure_participant(
                Participant,
                ap.user_id,
                personas=["alumni"],
                primary="alumni",
            )
            if participant.alumni_profile_id != ap.pk:
                participant.alumni_profile_id = ap.pk
                participant.save(update_fields=["alumni_profile", "updated_at"])


def reverse_noop(apps, schema_editor):
    """No-op reverse — Participant/TrackingRecord/TrackingMilestone tables are
    re-created from forward signals; we don't tear down rebuilt rows."""
    return None


class Migration(migrations.Migration):

    dependencies = [
        ("mel_tracking", "0006_participant_impactscore_baselinerecord_and_more"),
        ("alumni_profiles", "0001_initial"),
        ("smehub_onboarding", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(backfill, reverse_noop),
    ]
