# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-02-26 04:06
from __future__ import unicode_literals
from collections import defaultdict
from typing import List, Tuple

from django.contrib.postgres.aggregates import ArrayAgg
from django.db import migrations, models

from resolwe.permissions.models import Permission


def _get_permissions(queryset: models.QuerySet, apps) -> Tuple[List, List]:
    """Get all the user and group permissions for given ctype.

    :returns: dictionary with keys 'user' and 'group'. Each contains a
    dictionary with object primary ids as keys and list of tuples of the form
    ({user/group_id}, {permission}) as values.
    """
    GroupObjectPermission = apps.get_model("guardian", "GroupObjectPermission")
    UserObjectPermission = apps.get_model("guardian", "UserObjectPermission")
    ContentType = apps.get_model("contenttypes", "ContentType")

    ctype = ContentType.objects.get_for_model(queryset.model)

    def permission_from_strings(permission_names: List[str]) -> Permission:
        """Return the top permission from permission string.

        The permission strings are in the form '{permission_name}_{model}', for
        instance 'view_data'.
        """
        return max(
            Permission.from_name(permission_name.split("_")[0])
            for permission_name in permission_names
        )

    permission_models = {"user": UserObjectPermission, "group": GroupObjectPermission}

    perms = dict()
    for permission_for in permission_models:
        perms[permission_for] = (
            permission_models[permission_for]
            .objects.filter(content_type=ctype)
            .values("object_pk", entity_id=models.F(f"{permission_for}_id"))
            .annotate(permission=ArrayAgg("permission__codename"))
        )

    permission_maps = {"user": defaultdict(list), "group": defaultdict(list)}
    for permission_for in permission_models:
        for perm in perms[permission_for]:
            permission_maps[permission_for][int(perm["object_pk"])].append(
                (int(perm["entity_id"]), permission_from_strings(perm["permission"]))
            )
    return permission_maps


def migrate_old_permissions(apps, schema_editor):
    """Migrate old Guardian permissions to new permission architecture."""

    def process_queryset(queryset: models.QuerySet):
        """Create PermissionModel instances for the queryset."""

        permissions = _get_permissions(queryset, apps)
        for obj in queryset.iterator():
            if obj.permission_group is None:
                obj.permission_group = PermissionGroup.objects.create()
                obj.save()
            for entity_type in ["user", "group"]:
                for entity_id, permission in permissions[entity_type][obj.id]:
                    permission_models.append(
                        PermissionModel(
                            **{
                                "value": permission.value,
                                "permission_group": obj.permission_group,
                                f"{entity_type}_id": entity_id,
                            }
                        )
                    )

    Data = apps.get_model("flow", "Data")
    Entity = apps.get_model("flow", "Entity")
    Collection = apps.get_model("flow", "Collection")
    DescriptorSchema = apps.get_model("flow", "DescriptorSchema")
    Process = apps.get_model("flow", "Process")

    PermissionGroup = apps.get_model("permissions", "PermissionGroup")
    PermissionModel = apps.get_model("permissions", "PermissionModel")

    # All PermissionModel instances that will be bulk created at the end.
    permission_models = []

    # First process Collections and all its contents.
    process_queryset(Collection.objects.all())
    for collection in Collection.objects.all():
        collection.entity_set.update(permission_group_id=collection.permission_group_id)
        collection.data.update(permission_group_id=collection.permission_group_id)
        collection.relation_set.update(
            permission_group_id=collection.permission_group_id
        )

    # Now process entities without collection, that is entities with permission
    # group set to null in the database.
    remaining_entities = Entity.objects.filter(permission_group__isnull=True)
    # A list is needed of filter bellow will always return empty set
    remaining_entities_list = list(remaining_entities)
    process_queryset(remaining_entities)
    for entity in remaining_entities_list:
        entity.data.update(permission_group_id=entity.permission_group_id)

    # Now process remaining data objects, that is data objects with permission
    # group set to null in the database.
    remaining_data = Data.objects.filter(permission_group__isnull=True)
    process_queryset(remaining_data)

    # Now process descriptor schemas.
    process_queryset(DescriptorSchema.objects.all())

    # Now process processes.
    process_queryset(Process.objects.all())

    # Now bulk create all permission models.
    PermissionModel.objects.bulk_create(permission_models)


class Migration(migrations.Migration):

    dependencies = [
        ("permissions", "0001_initial"),
        ("flow", "0003_add_permission_group"),
    ]

    operations = [
        migrations.RunPython(
            migrate_old_permissions, reverse_code=migrations.RunPython.noop
        ),
    ]
