# Generated by Hand
from sentry.utils.query import RangeQuerySetWrapperWithProgressBar
from django.db import migrations

# SearchVisitor.numeric_keys + SearchVisitor.date_keys
OPERATOR_KEYS = {
    "project_id",
    "project.id",
    "issue.id",
    "device.battery_level",
    "device.charging",
    "device.online",
    "device.simulator",
    "error.handled",
    "stack.colno",
    "stack.in_app",
    "stack.lineno",
    "stack.stack_level",
    "transaction.duration",
    "apdex",
    "impact",
    "p75",
    "p95",
    "p99",
    "error_rate",
    "start",
    "end",
    "first_seen",
    "last_seen",
    "time",
    "timestamp",
    "transaction.start_time",
    "transaction.end_time",
}


# Aggregates are now fields
def convert_field(fieldname, unique, reverse):
    if fieldname == "count":
        fieldname = "count()"
    elif unique:
        fieldname = f"count_unique({fieldname})"

    fieldname = f"-{fieldname}" if reverse else fieldname
    return fieldname


def prepare_value(value):
    value = value.replace("%", "*")
    if " " in value and not value.startswith('"'):
        value = f'"{value}"'
    return value


def convert(
    DiscoverSavedQuery,
    DiscoverSavedQueryProject,
    saved_query,
    name_extra=" (migrated from legacy discover)",
):
    """ Create a v2 query from a v1 query"""
    if saved_query.version == 2:
        # nothing to do! Already v2 :)
        return saved_query

    updated_query = {
        "environment": [],
        "fields": saved_query.query.get("fields", []),
        "orderby": "",
        "query": [],  # Will become a string later via join
    }

    if "range" in saved_query.query:
        updated_query["range"] = saved_query.query["range"]
    elif "start" in saved_query.query and "end" in saved_query.query:
        updated_query["start"] = saved_query.query["start"]
        updated_query["end"] = saved_query.query["end"]
    else:
        updated_query["range"] = "14d"

    for aggregate in saved_query.query.get("aggregations", []):
        if aggregate[0] == "uniq":
            field = convert_field(aggregate[1], True, False)
        else:
            field = convert_field(aggregate[0], False, False)
        if field:
            updated_query["fields"].append(field)

    # Order by
    orderby = saved_query.query.get("orderby", "")
    unique = reverse = False
    if orderby.startswith("-"):
        reverse = True
        orderby = orderby[1:]
    if orderby.startswith("uniq_"):
        unique = True
        orderby = orderby[5:].replace("_", ".")
    field = convert_field(orderby, unique, reverse)

    if field:
        updated_query["orderby"] = field
        if reverse:
            field = field[1:]
        if field not in updated_query["fields"]:
            updated_query["fields"].append(field)

    # Conditions become a query now
    for condition in saved_query.query.get("conditions", []):
        column, operator, value = condition
        if column in ["contexts.key"]:
            column = "tags[contexts.key]"
        if column == "environment" and operator == "=":
            updated_query["environment"].append(value.strip('"'))
        elif operator == "IS NOT NULL":
            updated_query["query"].append(f"has:{column}")
        elif operator == "IS NULL":
            updated_query["query"].append(f"!has:{column}")
        elif column in OPERATOR_KEYS:
            updated_query["query"].append(
                "{}:{}{}".format(column, operator if operator != "=" else "", value)
            )
        elif operator in ["LIKE", "="]:
            updated_query["query"].append(f"{column}:{prepare_value(value)}")
        elif operator in ["NOT LIKE", "!="]:
            updated_query["query"].append(f"!{column}:{prepare_value(value)}")
    updated_query["query"] = " ".join(updated_query["query"])

    # Create the version 2 query
    new_query = DiscoverSavedQuery.objects.create(
        organization=saved_query.organization,
        name=saved_query.name + name_extra,
        query=updated_query,
        version=2,
    )

    # Set project_ids
    saved_query_project_ids = DiscoverSavedQueryProject.objects.filter(
        discover_saved_query=saved_query
    ).values_list("project", flat=True)

    # This is DiscoverSavedQueryProject.set_projects
    DiscoverSavedQueryProject.objects.filter(discover_saved_query=new_query).exclude(
        project__in=saved_query_project_ids
    ).delete()

    existing_project_ids = DiscoverSavedQueryProject.objects.filter(
        discover_saved_query=new_query
    ).values_list("project", flat=True)

    new_project_ids = list(set(saved_query_project_ids) - set(existing_project_ids))

    DiscoverSavedQueryProject.objects.bulk_create(
        [
            DiscoverSavedQueryProject(project_id=project_id, discover_saved_query=new_query)
            for project_id in new_project_ids
        ]
    )

    return new_query


def migrate_v1_queries(apps, schema_editor):
    """
    Creates v2 versions of existing v1 queries
    """
    DiscoverSavedQuery = apps.get_model("sentry", "DiscoverSavedQuery")
    DiscoverSavedQueryProject = apps.get_model("sentry", "DiscoverSavedQueryProject")

    """ Seq Scan on sentry_discoversavedquery
        (cost=0.00..102.86 rows=1601 width=284)
        (actual time=0.027..1.158 rows=1275 loops=1)
        Filter: (version = 1)
        Rows Removed by Filter: 69
        Planning time: 0.929 ms
        Execution time: 1.296 ms
    """
    queryset = DiscoverSavedQuery.objects.filter(version=1)

    for query in RangeQuerySetWrapperWithProgressBar(queryset):
        convert(DiscoverSavedQuery, DiscoverSavedQueryProject, query)


class Migration(migrations.Migration):
    # This flag is used to mark that a migration shouldn't be automatically run in
    # production. We set this to True for operations that we think are risky and want
    # someone from ops to run manually and monitor.
    # General advice is that if in doubt, mark your migration as `is_dangerous`.
    # Some things you should always mark as dangerous:
    # - Adding indexes to large tables. These indexes should be created concurrently,
    #   unfortunately we can't run migrations outside of a transaction until Django
    #   1.10. So until then these should be run manually.
    # - Large data migrations. Typically we want these to be run manually by ops so that
    #   they can be monitored. Since data migrations will now hold a transaction open
    #   this is even more important.
    # - Adding columns to highly active tables, even ones that are NULL.
    is_dangerous = False
    atomic = False
    dependencies = [
        ("sentry", "0028_user_reports"),
    ]

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