# Generated by Django 4.2.9 on 2025-01-03 16:12

from django.db import migrations, models, transaction
from django.conf import settings

from funkwhale_api.federation import utils as federation_utils
from django.urls import reverse
import uuid


def insert_tracks_to_playlist(apps, playlist, uploads):
    PlaylistTrack = apps.get_model("playlists", "PlaylistTrack")
    for i, upload in enumerate(uploads):
        if upload.track:
            PlaylistTrack.objects.create(
                creation_date=playlist.creation_date,
                playlist=playlist,
                track=upload.track,
                index=0 + i,
                uuid=(new_uuid := uuid.uuid4()),
                fid=federation_utils.full_url(
                    reverse(
                        "federation:music:playlist-tracks-detail",
                        kwargs={"uuid": new_uuid},
                    )
                ),
            )
            upload.library = None
            upload.save()

    playlist.library.playlist_uploads.set(uploads)


@transaction.atomic
def migrate_libraries_to_playlist(apps, schema_editor):
    Playlist = apps.get_model("playlists", "Playlist")
    Library = apps.get_model("music", "Library")
    Actor = apps.get_model("federation", "Actor")
    Channel = apps.get_model("audio", "Channel")

    to_instance_libs = []
    to_public_libs = []
    to_me_libs = []
    for library in Library.objects.all():
        if (
            not federation_utils.is_local(library.actor.fid)
            or library.actor.name == "service"
        ):
            continue

        if (
            hasattr(library, "playlist")
            and library.playlist
            and library.uploads.all().exists()
        ):
            uploads = library.uploads.all()
            with transaction.atomic():
                insert_tracks_to_playlist(apps, library.playlist, uploads)
            continue

        if (
            Channel.objects.filter(library=library).exists()
            or Playlist.objects.filter(library=library).exists()
            or not federation_utils.is_local(library.fid)
            or library.name in ["me", "instance", "everyone"]
        ):
            continue

        try:
            playlist, created = Playlist.objects.get_or_create(
                name=library.name,
                library=library,
                actor=library.actor,
                creation_date=library.creation_date,
                privacy_level=library.privacy_level,
                description=library.description,
                defaults={
                    "uuid": (new_uuid := uuid.uuid4()),
                    "fid": federation_utils.full_url(
                        reverse(
                            "federation:music:playlists-detail",
                            kwargs={"uuid": new_uuid},
                        )
                    ),
                },
            )
            playlist.save()

            if library.uploads.all().exists():
                uploads = library.uploads.all()
                with transaction.atomic():
                    insert_tracks_to_playlist(apps, playlist, uploads)

            if library.privacy_level == "me":
                to_me_libs.append(library)
            if library.privacy_level == "instance":
                to_instance_libs.append(library)
            if library.privacy_level == "everyone":
                to_public_libs.append(library)

            library.privacy_level = "me"
            library.playlist = playlist
            library.save()
        except Exception as e:
            print(f"An error occurred during library.playlist creation : {e}")
            continue

    # migrate uploads to new built-in libraries
    for actor in Actor.objects.all():
        if (
            not federation_utils.is_local(actor.fid)
            or actor.name == "service"
            or hasattr(actor, "channel")
        ):
            continue

        privacy_levels = ["me", "instance", "everyone"]
        for privacy_level in privacy_levels:
            build_in_lib, created = Library.objects.filter(
                channel__isnull=True
            ).get_or_create(
                actor=actor,
                privacy_level=privacy_level,
                name=privacy_level,
                defaults={
                    "uuid": (new_uuid := uuid.uuid4()),
                    "fid": federation_utils.full_url(
                        reverse(
                            "federation:music:libraries-detail",
                            kwargs={"uuid": new_uuid},
                        )
                    ),
                },
            )
            for library in actor.libraries.filter(privacy_level=privacy_level):
                library.uploads.all().update(library=build_in_lib)
                library.delete

            if privacy_level == "everyone":
                for lib in to_public_libs:
                    lib.uploads.all().update(library=build_in_lib)
            if privacy_level == "instance":
                for lib in to_instance_libs:
                    lib.uploads.all().update(library=build_in_lib)
            if privacy_level == "me":
                for lib in to_me_libs:
                    lib.uploads.all().update(library=build_in_lib)


def check_succefull_migration(apps, schema_editor):
    Actor = apps.get_model("federation", "Actor")
    Playlist = apps.get_model("playlists", "Playlist")

    for actor in Actor.objects.all():
        not_build_in_libs = (
            actor.playlists.count()
            + actor.libraries.filter(channel__isnull=False).count()
        )

        if actor.name == "service" or not federation_utils.is_local(actor.fid):
            continue

        elif actor.playlists.filter(library__isnull=True).count() > 0:
            raise Exception(
                f"Incoherent playlist database state : all local playlists do not have lib or too many libs"
            )
        elif (
            not hasattr(actor, "channel")
            and actor.libraries.count() - 3 != not_build_in_libs
            or (hasattr(actor, "channel") and actor.libraries.count() > 1)
        ):
            raise Exception(
                f"Incoherent library database state, check for errors in log and share them to the funkwhale team. Migration was abordted to prevent data loss.\
                    actor libs = {actor.libraries.count()} and acto not built-in lib = {not_build_in_libs} \
                    and acto pl ={actor.playlists.count()} and not channel lib = {actor.libraries.filter(channel__isnull=False).count()} \
                    and actor.name = {actor.name}"
            )
    for playlist in Playlist.objects.all():
        if not federation_utils.is_local(playlist.fid):
            continue
        elif playlist.library.privacy_level != "me":
            raise Exception(
                "Incoherent playlist database state, check for errors in log and share them to the funkwhale team. Migration was abordted to prevent data loss"
            )


class Migration(migrations.Migration):
    dependencies = [
        ("music", "0060_empty_for_test"),
        ("playlists", "0009_playlist_library"),
    ]
    operations = [
        migrations.AddField(
            model_name="upload",
            name="playlist_libraries",
            field=models.ManyToManyField(
                blank=True,
                related_name="playlist_uploads",
                to="music.library",
            ),
        ),
        migrations.RunPython(
            migrate_libraries_to_playlist, reverse_code=migrations.RunPython.noop
        ),
        migrations.RunPython(
            check_succefull_migration, reverse_code=migrations.RunPython.noop
        ),
    ]
