# Generated by Django 4.2.19 on 2025-05-21 13:50

import django.contrib.postgres.fields
from django.db import migrations, models
from pulpcore.plugin.migrations import RequireVersion


def migrate_missing_tags_up(apps, schema):
    CollectionVersion = apps.get_model("ansible", "CollectionVersion")
    cv_to_update = []
    for cv in CollectionVersion.objects.filter(new_tags=None).only("new_tags").prefetch_related("tags"):
        cv.new_tags = [t.name for t in cv.tags.all()]
        cv_to_update.append(cv)
        if len(cv_to_update) >= 100:
            CollectionVersion.objects.bulk_update(cv_to_update, ["new_tags"])
            cv_to_update = []
    if len(cv_to_update) > 0:
        CollectionVersion.objects.bulk_update(cv_to_update, ["new_tags"])


def migrate_missing_tags_down(apps, schema):
    # Warning, this Down-Migration is not Zero-Downtime safe.
    # Also it's not optimized.
    Tag = apps.get_model("ansible", "Tag")
    CollectionVersion = apps.get_model("ansible", "CollectionVersion")
    for cv in CollectionVersion.objects.filter(new_tags__len__gt=0).only("new_tags"):
        for tag_name in cv.new_tags:
            tag, created = Tag.objects.get_or_create(name=tag_name)
            cv.tags.add(tag)


# This query generates full text search index based
# the following data ranked from A to D:
#   - Namespace name (weight A)
#   - Collection name (weight A)
#   - Collection tags (weight B)
#   - Collection content names (weight C)
#   - Collection description (weight D)
NEW_TS_VECTOR_SELECT = """
    setweight(to_tsvector(coalesce(NEW.namespace,'')), 'A')
    || setweight(to_tsvector(coalesce(NEW.name, '')), 'A')
    || setweight(to_tsvector(array_to_string(coalesce(NEW.tags, ARRAY[]::bpchar[]), ' ')), 'B')
    || (
      SELECT
        setweight(to_tsvector(coalesce(string_agg(cvc ->> 'name', ' '), '')), 'C')
      FROM jsonb_array_elements(NEW.contents) AS cvc
    )
    || setweight(to_tsvector(coalesce(NEW.description, '')), 'D')
"""

OLD_TS_VECTOR_SELECT = """
    SELECT
    setweight(to_tsvector(coalesce(namespace,'')), 'A')
    || setweight(to_tsvector(coalesce(name, '')), 'A')
    || (
      SELECT
        setweight(to_tsvector(
          coalesce(string_agg("ansible_tag"."name", ' '), '')
        ), 'B')
      FROM
        "ansible_tag" INNER JOIN "ansible_collectionversion_tags"
        ON
        ("ansible_tag"."pulp_id" = "ansible_collectionversion_tags"."tag_id" )
      WHERE
        "ansible_collectionversion_tags"."collectionversion_id" = cv.content_ptr_id
    )
    || (
      SELECT
        setweight(to_tsvector(
          coalesce(string_agg(cvc ->> 'name', ' '), '')
        ), 'C')
      FROM jsonb_array_elements(cv.contents) AS cvc
    )
    || setweight(to_tsvector(coalesce(description, '')), 'D')
    FROM ansible_collectionversion cv
    WHERE cv.content_ptr_id = NEW.content_ptr_id
"""

# Creates a database function and a trigger to update collection search
# vector field.
# In the new version, tags are availabel at create and content should never be updated,
# so having both insert and update in the trigger from now on is probably fast _and_ safe.
UPDATE_COLLECTIONS_TS_VECTOR_TRIGGER = """
SET CONSTRAINTS ALL IMMEDIATE;
CREATE OR REPLACE FUNCTION update_collection_ts_vector()
    RETURNS TRIGGER AS
$$
BEGIN
    NEW.search_vector := ({TS_VECTOR_SELECT});
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS update_ts_vector ON ansible_collectionversion;
CREATE TRIGGER update_ts_vector
    {WHEN}
    ON ansible_collectionversion
    FOR EACH ROW
EXECUTE FUNCTION update_collection_ts_vector();
"""


class Migration(migrations.Migration):

    dependencies = [
        ("ansible", "0061_collectionversion_new_tags"),
    ]

    operations = [
        RequireVersion("ansible", "0.26.0"),
        migrations.RunPython(code=migrate_missing_tags_up, reverse_code=migrate_missing_tags_down),
        migrations.RunSQL(
            sql=UPDATE_COLLECTIONS_TS_VECTOR_TRIGGER.format(
                TS_VECTOR_SELECT=NEW_TS_VECTOR_SELECT,
                WHEN="BEFORE INSERT OR UPDATE",
            ),
            reverse_sql=UPDATE_COLLECTIONS_TS_VECTOR_TRIGGER.format(
                TS_VECTOR_SELECT=OLD_TS_VECTOR_SELECT,
                WHEN="BEFORE UPDATE",
            ),
        ),
        migrations.RemoveField(
            model_name="collectionversion",
            name="tags",
        ),
        migrations.DeleteModel(
            name="Tag",
        ),
        migrations.RenameField(
            model_name="collectionversion",
            old_name="new_tags",
            new_name="tags",
        ),
        migrations.AlterField(
            model_name="collectionversion",
            name="tags",
            field=django.contrib.postgres.fields.ArrayField(
                base_field=models.CharField(max_length=64, unique=True), default=list, size=None
            ),
        ),
    ]
