# Generated by Django 3.2.18 on 2023-03-20 13:08

from django.db import migrations
from django.db.models import Q

from semantic_version import Version


#####################################################
# COPIED FROM tasks/collectionversion_index.py
#####################################################

def has_distribution(repository, repository_version, apps=None):
    """Is -any- distribution attached to this repo or repo version?"""
    AnsibleDistribution = apps.get_model('ansible', 'AnsibleDistribution')
    return AnsibleDistribution.objects.filter(
        Q(repository=repository) | Q(repository_version=repository_version)
    ).exists()


def get_highest_version_string_from_cv_objects(cv_objects):
    """Return the highest version as a string (preferring stable releases)."""
    versions_strings = [x.version for x in cv_objects]
    versions = [Version(x) for x in versions_strings]
    versions = sorted(versions, reverse=True)
    stable_versions = [x for x in versions if not x.prerelease]
    if stable_versions:
        return str(stable_versions[0])
    return str(versions[0])


def get_highest_repository_version(apps, repository):
    RepositoryVersion = apps.get_model('core', 'RepositoryVersion')
    return RepositoryVersion.objects.filter(repository=repository).order_by('number').last()


def get_repository_version_content(apps, repository_version):
    Content = apps.get_model('core', 'Content')
    RepositoryContent = apps.get_model('core', 'RepositoryContent')

    # https://github.com/pulp/pulpcore/blob/main/pulpcore/app/models/repository.py#L660-L662
    content_relationships = RepositoryContent.objects.filter(
        repository=repository_version.repository, version_added__number__lte=repository_version.number
    ).exclude(version_removed__number__lte=repository_version.number)

    # https://github.com/pulp/pulpcore/blob/main/pulpcore/app/models/repository.py#L687
    return Content.objects.filter(version_memberships__in=content_relationships)


def update_index(apps=None, distribution=None, repository=None, repository_version=None, is_latest=False):
    """Rebuild index by distribtion|repository|repositoryversion."""

    # deferred model import ...
    AnsibleCollectionDeprecated = apps.get_model('ansible', 'AnsibleCollectionDeprecated')
    AnsibleNamespace = apps.get_model('ansible', 'AnsibleNamespace')
    AnsibleRepository = apps.get_model('ansible', 'AnsibleRepository')
    CollectionVersion = apps.get_model('ansible', 'CollectionVersion')
    CollectionVersionSignature = apps.get_model('ansible', 'CollectionVersionSignature')
    CVIndex = apps.get_model('ansible', 'CrossRepositoryCollectionVersionIndex')

    # if the distro points at a specific repo version, we should use that in the index
    # otherwise the index value for repository version should be null
    # use_repository_version = False
    use_repository_version = not is_latest

    # a repov was passed in so we should use that
    # if repository_version:
    #    use_repository_version = True

    # make sure the distro points at a repo[version]
    if distribution and not repository and not repository_version:
        # sometimes distros point at a version
        if distribution.repository_version:
            repository = distribution.repository_version.repository
            repository_version = distribution.repository_version
            use_repository_version = True

        # sometimes distros point at a repository
        elif distribution.repository is not None:
            repository = distribution.repository
            if hasattr(distribution.repository, 'latest_version'):
                repository_version = distribution.repository.latest_version()
            else:
                repository_version = get_highest_repository_version(apps, distribution.repository)
            use_repository_version = False

        # sometimes distros point at nothing?
        else:
            return

    # extract repository from repository version if needed
    if repository is None:
        repository = repository_version.repository

    # optimization: -must- have an AnsibleRepository for the index model
    if not isinstance(repository, AnsibleRepository):
        repository = AnsibleRepository.objects.filter(pk=repository.pk).first()
        if repository is None:
            return

    # optimization: we only want to index "distributed" CVs
    if distribution is None and not has_distribution(repository, repository_version, apps=apps):
        return

    # This block handles a case where the distribution that points at a repository
    # has been deleted. If no other distribution points at the repository, all related
    # indexes need to be removed and to exit early.
    if not use_repository_version:
        if not has_distribution(repository, repository_version, apps=apps):
            CVIndex.objects.filter(repository=repository, repository_version=None).delete()
            return

    # optimizaion: exit early if using a repo version and it's alreay indexed
    if use_repository_version:
        if CVIndex.objects.filter(repository_version=repository_version).exists():
            return

    # get all CVs in this repository version
    if hasattr(repository_version, 'content'):
        cvs = repository_version.content.filter(pulp_type="ansible.collection_version").values_list(
            "pk", flat=True
        )
    else:
        repov_content = get_repository_version_content(apps, repository_version)
        cvs = repov_content.filter(pulp_type="ansible.collection_version").values_list(
            "pk", flat=True
        )

    cvs = CollectionVersion.objects.filter(pk__in=cvs)

    # clean out cvs no longer in the repo when a distro w/ a repo
    if not use_repository_version:
        CVIndex.objects.filter(repository=repository, repository_version=None).exclude(
            collection_version__pk__in=cvs
        ).delete()

    # get the set of signatures in this repo version
    if hasattr(repository_version, 'content'):
        repo_signatures_pks = repository_version.content.filter(
            pulp_type="ansible.collection_signature"
        ).values_list("pk", flat=True)
    else:
        repov_content = get_repository_version_content(apps, repository_version)
        repo_signatures_pks = repov_content.filter(
            pulp_type="ansible.collection_signature"
        ).values_list("pk", flat=True)
    repo_signatures = CollectionVersionSignature.objects.filter(pk__in=repo_signatures_pks)

    # get the set of deprecations in this repo version
    if hasattr(repository_version, 'content'):
        deprecations = repository_version.content.filter(
            pulp_type="ansible.collection_deprecation"
        ).values_list("pk", flat=True)
    else:
        repov_content = get_repository_version_content(apps, repository_version)
        deprecations = repov_content.filter(
            pulp_type="ansible.collection_deprecation"
        ).values_list("pk", flat=True)
    deprecations = AnsibleCollectionDeprecated.objects.filter(pk__in=deprecations)
    deprecations_set = {(x.namespace, x.name) for x in deprecations}

    # find all namespaces in the repo version
    if hasattr(repository_version, 'content'):
        namespaces = repository_version.content.filter(pulp_type="ansible.namespace").values_list(
            "pk", flat=True
        )
    else:
        repov_content = get_repository_version_content(apps, repository_version)
        namespaces = repov_content.filter(pulp_type="ansible.namespace").values_list(
            "pk", flat=True
        )
    namespaces = AnsibleNamespace.objects.filter(pk__in=namespaces)
    namespaces = {x.name: x for x in namespaces}

    # map out the namespace(s).name(s) for everything in the repo version
    colset = set(cvs.values_list("namespace", "name").distinct())

    repo_v = None
    if use_repository_version:
        repo_v = repository_version

    # iterate through each collection in the repository
    for colkey in colset:
        namespace, name = colkey

        # get all the versions for this collection
        related_cvs = cvs.filter(namespace=namespace, name=name).only("version")

        # what is the "highest" version in this list?
        highest_version = get_highest_version_string_from_cv_objects(related_cvs)

        # should all of these CVs be deprecated?
        is_deprecated = colkey in deprecations_set

        # process each related CV
        for rcv in related_cvs:
            # get the related signatures for this CV
            rcv_signatures = repo_signatures.filter(signed_collection=rcv).count()

            # create|update the index for this CV
            CVIndex.objects.update_or_create(
                repository=repository,
                repository_version=repo_v,
                collection_version=rcv,
                defaults={
                    "is_highest": rcv.version == highest_version,
                    "is_signed": rcv_signatures > 0,
                    "is_deprecated": is_deprecated,
                    "namespace_metadata": namespaces.get(namespace, None),
                },
            )


def rebuild_index(apps):
    """Rebuild -everything-."""

    indexed_repos = set()

    AnsibleDistribution = apps.get_model('ansible', 'AnsibleDistribution')
    dqs = AnsibleDistribution.objects.select_related(
        "repository", "repository_version", "repository_version__repository"
    ).all()
    for distro in dqs:
        if distro.repository_version:
            rv = distro.repository_version
        else:
            if hasattr(distro.repository, 'latest_version'):
                rv = distro.repository.latest_version()
            else:
                rv = get_highest_repository_version(apps, distro.repository)

        if rv.pk in indexed_repos:
            continue

        update_index(distribution=distro, apps=apps)
        indexed_repos.add(rv.pk)


#####################################################
# MIGRATION CODE
#####################################################

def force_build(apps, *args, **kwargs):
    rebuild_index(apps)


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0102_add_domain_relations'),
        ('ansible', '0050_crossrepositorycollectionversionindex'),
    ]

    operations = [
       migrations.RunPython(
            code=force_build,
            reverse_code=migrations.RunPython.noop,
            elidable=True,
       ),
    ]
