# Generated by Django 3.0.13 on 2021-04-04 06:40

import bisect
from sys import stdout

from django.db import migrations
from univers.versions import version_class_by_package_type


def nearest_patched_versions(vulnerable_versions, resolved_versions):
    """
    Returns a mapping of vulnerable_version -> nearest_safe_version
    """

    vulnerable_versions = sorted(vulnerable_versions)
    resolved_versions = sorted(resolved_versions)
    resolved_version_count = len(resolved_versions)
    nearest_patch_for_version = {}
    for vulnerable_version in vulnerable_versions:
        nearest_patch_for_version[vulnerable_version] = None
        if not resolved_versions:
            continue

        patched_version_index = bisect.bisect_right(resolved_versions, vulnerable_version)
        if patched_version_index >= resolved_version_count:
            continue
        nearest_patch_for_version[vulnerable_version] = resolved_versions[patched_version_index]

    return nearest_patch_for_version


def _get_tuple_key(packagerelatedvulnerability):
    return (
        packagerelatedvulnerability.vulnerability,
        packagerelatedvulnerability.package.type,
        packagerelatedvulnerability.package.name,
    )


def create_patched_candidates_by_tuple_key(packagerelatedvulnerability_class):
    """
    Creates and returns a mapping of form
    (models.Vulnerability, models.Package.type, models.Package.name) -> List[models.PackageRelatedVulnerability]
    It's used to prevent multiple hits to db in firther processing.
    """
    patched_candidates = {}
    for prv in packagerelatedvulnerability_class.objects.filter(
        is_vulnerable=False
    ).select_related():
        key_tuple = _get_tuple_key(prv)
        if key_tuple in patched_candidates:
            patched_candidates[key_tuple].append(prv.package)
        else:
            patched_candidates[key_tuple] = [prv.package]

    return patched_candidates


def populate_patched_packages(apps, schema_editor):
    PackageRelatedVulnerability = apps.get_model("vulnerabilities", "PackageRelatedVulnerability")
    patched_candidates = create_patched_candidates_by_tuple_key(PackageRelatedVulnerability)
    tot = PackageRelatedVulnerability.objects.filter(is_vulnerable=True).count()
    n = 0
    for prv in PackageRelatedVulnerability.objects.filter(is_vulnerable=True).select_related():
        stdout.write(f"{n}/{tot}\n")
        n += 1
        key_tuple = _get_tuple_key(prv)
        if key_tuple not in patched_candidates:
            continue

        possible_patches = patched_candidates[key_tuple]
        version_class = version_class_by_package_type[prv.package.type]
        patched_package_by_version_obj = {}

        for patch in possible_patches:
            try:
                patched_package_by_version_obj[version_class(patch.version)] = patch
            except Exception as e:
                stdout.write(f"{e} {patch.version}")
                continue
        patched_version_objects = list(patched_package_by_version_obj.keys())

        try:
            target_version_object = version_class(prv.package.version)
            nearest_patched_version = nearest_patched_versions(
                [target_version_object], patched_version_objects
            )[target_version_object]
        except Exception as e:
            stdout.write(f"{e}, {prv.package.version}")
            continue

        if not nearest_patched_version:
            continue

        prv.patched_package = patched_package_by_version_obj[nearest_patched_version]
        prv.save()

    PackageRelatedVulnerability.objects.filter(is_vulnerable=False).delete()


class Migration(migrations.Migration):

    dependencies = [
        ("vulnerabilities", "0002_add_patched_package"),
    ]

    operations = [migrations.RunPython(populate_patched_packages)]

# Generated by Django 3.0.13 on 2021-04-04 06:40

import bisect
from sys import stdout

from django.db import migrations
from univers.versions import version_class_by_package_type


def nearest_patched_versions(vulnerable_versions, resolved_versions):
    """
    Returns a mapping of vulnerable_version -> nearest_safe_version
    """

    vulnerable_versions = sorted(vulnerable_versions)
    resolved_versions = sorted(resolved_versions)
    resolved_version_count = len(resolved_versions)
    nearest_patch_for_version = {}
    for vulnerable_version in vulnerable_versions:
        nearest_patch_for_version[vulnerable_version] = None
        if not resolved_versions:
            continue

        patched_version_index = bisect.bisect_right(resolved_versions, vulnerable_version)
        if patched_version_index >= resolved_version_count:
            continue
        nearest_patch_for_version[vulnerable_version] = resolved_versions[patched_version_index]

    return nearest_patch_for_version


def _get_tuple_key(packagerelatedvulnerability):
    return (
        packagerelatedvulnerability.vulnerability,
        packagerelatedvulnerability.package.type,
        packagerelatedvulnerability.package.name,
    )


def create_patched_candidates_by_tuple_key(packagerelatedvulnerability_class):
    """
    Creates and returns a mapping of form
    (models.Vulnerability, models.Package.type, models.Package.name) -> List[models.PackageRelatedVulnerability]
    It's used to prevent multiple hits to db in firther processing.
    """
    patched_candidates = {}
    for prv in packagerelatedvulnerability_class.objects.filter(
        is_vulnerable=False
    ).select_related():
        key_tuple = _get_tuple_key(prv)
        if key_tuple in patched_candidates:
            patched_candidates[key_tuple].append(prv.package)
        else:
            patched_candidates[key_tuple] = [prv.package]

    return patched_candidates


def populate_patched_packages(apps, schema_editor):
    PackageRelatedVulnerability = apps.get_model("vulnerabilities", "PackageRelatedVulnerability")
    patched_candidates = create_patched_candidates_by_tuple_key(PackageRelatedVulnerability)
    tot = PackageRelatedVulnerability.objects.filter(is_vulnerable=True).count()
    n = 0
    for prv in PackageRelatedVulnerability.objects.filter(is_vulnerable=True).select_related():
        stdout.write(f"{n}/{tot}\n")
        n += 1
        key_tuple = _get_tuple_key(prv)
        if key_tuple not in patched_candidates:
            continue

        possible_patches = patched_candidates[key_tuple]
        version_class = version_class_by_package_type[prv.package.type]
        patched_package_by_version_obj = {}

        for patch in possible_patches:
            try:
                patched_package_by_version_obj[version_class(patch.version)] = patch
            except Exception as e:
                stdout.write(f"{e} {patch.version}")
                continue
        patched_version_objects = list(patched_package_by_version_obj.keys())

        try:
            target_version_object = version_class(prv.package.version)
            nearest_patched_version = nearest_patched_versions(
                [target_version_object], patched_version_objects
            )[target_version_object]
        except Exception as e:
            stdout.write(f"{e}, {prv.package.version}")
            continue

        if not nearest_patched_version:
            continue

        prv.patched_package = patched_package_by_version_obj[nearest_patched_version]
        prv.save()

    PackageRelatedVulnerability.objects.filter(is_vulnerable=False).delete()


class Migration(migrations.Migration):

    dependencies = [
        ("vulnerabilities", "0002_add_patched_package"),
    ]

    operations = [migrations.RunPython(populate_patched_packages)]

# Generated by Django 3.0.13 on 2021-04-04 06:40 Returns a mapping of vulnerable_version -> nearest_safe_version Creates and returns a mapping of form     (models.Vulnerability, models.Package.type, models.Package.name) -> List[models.PackageRelatedVulnerability]     It's used to prevent multiple hits to db in firther processing.