from django.urls import reverse

from sentry.discover.models import DiscoverSavedQuery
from sentry.testutils import APITestCase, SnubaTestCase
from sentry.testutils.helpers.datetime import before_now, iso_format


class DiscoverSavedQueryBase(APITestCase, SnubaTestCase):
    def setUp(self):
        super().setUp()
        self.login_as(user=self.user)
        self.org = self.create_organization(owner=self.user)
        self.projects = [
            self.create_project(organization=self.org),
            self.create_project(organization=self.org),
        ]
        self.project_ids = [project.id for project in self.projects]
        self.project_ids_without_access = [self.create_project().id]
        query = {"fields": ["test"], "conditions": [], "limit": 10}

        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=self.user.id,
            name="Test query",
            query=query,
            version=1,
        )

        model.set_projects(self.project_ids)


class DiscoverSavedQueriesTest(DiscoverSavedQueryBase):
    feature_name = "organizations:discover"

    def setUp(self):
        super().setUp()
        self.url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])

    def test_get(self):
        with self.feature(self.feature_name):
            response = self.client.get(self.url)

        assert response.status_code == 200, response.content
        assert len(response.data) == 1
        assert response.data[0]["name"] == "Test query"
        assert response.data[0]["projects"] == self.project_ids
        assert response.data[0]["fields"] == ["test"]
        assert response.data[0]["conditions"] == []
        assert response.data[0]["limit"] == 10
        assert response.data[0]["version"] == 1
        assert "createdBy" in response.data[0]
        assert response.data[0]["createdBy"]["username"] == self.user.username
        assert not response.data[0]["expired"]

    def test_get_version_filter(self):
        with self.feature(self.feature_name):
            response = self.client.get(self.url, format="json", data={"query": "version:1"})

        assert response.status_code == 200, response.content
        assert len(response.data) == 1
        assert response.data[0]["name"] == "Test query"

        with self.feature(self.feature_name):
            response = self.client.get(self.url, format="json", data={"query": "version:2"})

        assert response.status_code == 200, response.content
        assert len(response.data) == 0

    def test_get_name_filter(self):
        with self.feature(self.feature_name):
            response = self.client.get(self.url, format="json", data={"query": "Test"})

        assert response.status_code == 200, response.content
        assert len(response.data) == 1
        assert response.data[0]["name"] == "Test query"

        with self.feature(self.feature_name):
            # Also available as the name: filter.
            response = self.client.get(self.url, format="json", data={"query": "name:Test"})

        assert response.status_code == 200, response.content
        assert len(response.data) == 1
        assert response.data[0]["name"] == "Test query"

        with self.feature(self.feature_name):
            response = self.client.get(self.url, format="json", data={"query": "name:Nope"})

        assert response.status_code == 200, response.content
        assert len(response.data) == 0

    def test_get_all_paginated(self):
        for i in range(0, 10):
            query = {"fields": ["test"], "conditions": [], "limit": 10}
            model = DiscoverSavedQuery.objects.create(
                organization=self.org,
                created_by_id=self.user.id,
                name=f"My query {i}",
                query=query,
                version=1,
            )
            model.set_projects(self.project_ids)

        with self.feature(self.feature_name):
            response = self.client.get(self.url, data={"per_page": 1})
        assert response.status_code == 200, response.content
        assert len(response.data) == 1

        with self.feature(self.feature_name):
            # The all parameter ignores pagination and returns all values.
            response = self.client.get(self.url, data={"per_page": 1, "all": 1})
        assert response.status_code == 200, response.content
        assert len(response.data) == 11

    def test_get_sortby(self):
        query = {"fields": ["message"], "query": "", "limit": 10}
        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=self.user.id,
            name="My query",
            query=query,
            version=2,
            date_created=before_now(minutes=10),
            date_updated=before_now(minutes=10),
        )
        model.set_projects(self.project_ids)

        sort_options = {
            "dateCreated": True,
            "-dateCreated": False,
            "dateUpdated": True,
            "-dateUpdated": False,
            "name": True,
            "-name": False,
        }
        for sorting, forward_sort in sort_options.items():
            with self.feature(self.feature_name):
                response = self.client.get(self.url, data={"sortBy": sorting})
            assert response.status_code == 200

            values = [row[sorting.strip("-")] for row in response.data]
            if not forward_sort:
                values = list(reversed(values))
            assert list(sorted(values)) == values

    def test_get_sortby_most_popular(self):
        query = {"fields": ["message"], "query": "", "limit": 10}
        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=self.user.id,
            name="My query",
            query=query,
            version=2,
            visits=3,
            date_created=before_now(minutes=10),
            date_updated=before_now(minutes=10),
            last_visited=before_now(minutes=5),
        )

        model.set_projects(self.project_ids)
        for forward_sort in [True, False]:
            sorting = "mostPopular" if forward_sort else "-mostPopular"
            with self.feature(self.feature_name):
                response = self.client.get(self.url, data={"sortBy": sorting})

            assert response.status_code == 200
            values = [row["name"] for row in response.data]
            expected = ["My query", "Test query"]

            if not forward_sort:
                expected = list(reversed(expected))

            assert values == expected

    def test_get_sortby_recently_viewed(self):
        query = {"fields": ["message"], "query": "", "limit": 10}
        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=self.user.id,
            name="My query",
            query=query,
            version=2,
            visits=3,
            date_created=before_now(minutes=10),
            date_updated=before_now(minutes=10),
            last_visited=before_now(minutes=5),
        )

        model.set_projects(self.project_ids)
        for forward_sort in [True, False]:
            sorting = "recentlyViewed" if forward_sort else "-recentlyViewed"
            with self.feature(self.feature_name):
                response = self.client.get(self.url, data={"sortBy": sorting})

            assert response.status_code == 200
            values = [row["name"] for row in response.data]
            expected = ["Test query", "My query"]

            if not forward_sort:
                expected = list(reversed(expected))

            assert values == expected

    def test_get_sortby_myqueries(self):
        uhoh_user = self.create_user(username="uhoh")
        self.create_member(organization=self.org, user=uhoh_user)

        whoops_user = self.create_user(username="whoops")
        self.create_member(organization=self.org, user=whoops_user)

        query = {"fields": ["message"], "query": "", "limit": 10}
        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=uhoh_user.id,
            name="a query for uhoh",
            query=query,
            version=2,
            date_created=before_now(minutes=10),
            date_updated=before_now(minutes=10),
        )
        model.set_projects(self.project_ids)

        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=whoops_user.id,
            name="a query for whoops",
            query=query,
            version=2,
            date_created=before_now(minutes=10),
            date_updated=before_now(minutes=10),
        )
        model.set_projects(self.project_ids)

        with self.feature(self.feature_name):
            response = self.client.get(self.url, data={"sortBy": "myqueries"})
        assert response.status_code == 200, response.content
        values = [int(item["createdBy"]["id"]) for item in response.data]
        assert values == [self.user.id, uhoh_user.id, whoops_user.id]

    def test_get_expired_query(self):
        query = {
            "start": iso_format(before_now(days=90)),
            "end": iso_format(before_now(days=61)),
        }
        DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=self.user.id,
            name="My expired query",
            query=query,
            version=2,
            date_created=before_now(days=90),
            date_updated=before_now(minutes=10),
        )
        with self.options({"system.event-retention-days": 60}), self.feature(self.feature_name):
            response = self.client.get(self.url, {"query": "name:My expired query"})

        assert response.status_code == 200, response.content
        assert response.data[0]["expired"]

    def test_get_ignores_homepage_queries(self):
        query = {"fields": ["test"], "conditions": [], "limit": 10}
        model = DiscoverSavedQuery.objects.create(
            organization=self.org,
            created_by_id=self.user.id,
            name="Homepage Test Query",
            query=query,
            version=2,
            date_created=before_now(minutes=10),
            date_updated=before_now(minutes=10),
            is_homepage=True,
        )
        model.set_projects(self.project_ids)

        with self.feature(self.feature_name):
            response = self.client.get(self.url)
        assert response.status_code == 200, response.content
        assert len(response.data) == 1
        assert not any([query["name"] == "Homepage Test Query" for query in response.data])

    def test_post(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "New query",
                    "projects": self.project_ids,
                    "fields": [],
                    "range": "24h",
                    "limit": 20,
                    "conditions": [],
                    "aggregations": [],
                    "orderby": "-time",
                },
            )
        assert response.status_code == 201, response.content
        assert response.data["name"] == "New query"
        assert response.data["projects"] == self.project_ids
        assert response.data["range"] == "24h"
        assert not hasattr(response.data, "start")
        assert not hasattr(response.data, "end")

    def test_post_invalid_projects(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "New query",
                    "projects": self.project_ids_without_access,
                    "fields": [],
                    "range": "24h",
                    "limit": 20,
                    "conditions": [],
                    "aggregations": [],
                    "orderby": "-time",
                },
            )
        assert response.status_code == 403, response.content

    def test_post_all_projects(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "All projects",
                    "projects": [-1],
                    "conditions": [],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "orderby": "time",
                },
            )
        assert response.status_code == 201, response.content
        assert response.data["projects"] == [-1]
        assert response.data["name"] == "All projects"

    def test_post_cannot_use_version_two_fields(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "New query",
                    "projects": self.project_ids,
                    "fields": ["id"],
                    "range": "24h",
                    "limit": 20,
                    "environment": ["dev"],
                    "yAxis": ["count(id)"],
                    "aggregations": [],
                    "orderby": "-time",
                },
            )
        assert response.status_code == 400, response.content
        assert (
            "You cannot use the environment, yAxis attribute(s) with the selected version"
            == response.data["non_field_errors"][0]
        )


class DiscoverSavedQueriesVersion2Test(DiscoverSavedQueryBase):
    feature_name = "organizations:discover-query"

    def setUp(self):
        super().setUp()
        self.url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])

    def test_post_invalid_conditions(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "New query",
                    "projects": self.project_ids,
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "version": 2,
                    "conditions": [["field", "=", "value"]],
                },
            )
        assert response.status_code == 400, response.content
        assert (
            "You cannot use the conditions attribute(s) with the selected version"
            == response.data["non_field_errors"][0]
        )

    def test_post_require_selected_fields(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "New query",
                    "projects": self.project_ids,
                    "fields": [],
                    "range": "24h",
                    "version": 2,
                },
            )
        assert response.status_code == 400, response.content
        assert "You must include at least one field." == response.data["non_field_errors"][0]

    def test_post_success(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "new query",
                    "projects": self.project_ids,
                    "fields": ["title", "count()", "project"],
                    "environment": ["dev"],
                    "query": "event.type:error browser.name:Firefox",
                    "range": "24h",
                    "yAxis": ["count(id)"],
                    "display": "releases",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        data = response.data
        assert data["fields"] == ["title", "count()", "project"]
        assert data["range"] == "24h"
        assert data["environment"] == ["dev"]
        assert data["query"] == "event.type:error browser.name:Firefox"
        assert data["yAxis"] == ["count(id)"]
        assert data["display"] == "releases"
        assert data["version"] == 2

    def test_post_all_projects(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "New query",
                    "projects": [-1],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        assert response.data["projects"] == [-1]

    def test_save_with_project(self):
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": self.project_ids,
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": f"project:{self.projects[0].slug}",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        assert DiscoverSavedQuery.objects.filter(name="project query").exists()

    def test_save_with_project_and_my_projects(self):
        team = self.create_team(organization=self.org, members=[self.user])
        project = self.create_project(organization=self.org, teams=[team])
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": [],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": f"project:{project.slug}",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        assert DiscoverSavedQuery.objects.filter(name="project query").exists()

    def test_save_with_org_projects(self):
        project = self.create_project(organization=self.org)
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": [project.id],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        assert DiscoverSavedQuery.objects.filter(name="project query").exists()

    def test_save_with_team_project(self):
        team = self.create_team(organization=self.org, members=[self.user])
        project = self.create_project(organization=self.org, teams=[team])
        self.create_project(organization=self.org, teams=[team])
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": [project.id],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        assert DiscoverSavedQuery.objects.filter(name="project query").exists()

    def test_save_without_team(self):
        team = self.create_team(organization=self.org, members=[])
        self.create_project(organization=self.org, teams=[team])
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "without team query",
                    "projects": [],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "version": 2,
                },
            )

        assert response.status_code == 400
        assert "No Projects found, join a Team" == response.data["detail"]

    def test_save_with_team_and_without_project(self):
        team = self.create_team(organization=self.org, members=[self.user])
        self.create_project(organization=self.org, teams=[team])
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "with team query",
                    "projects": [],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "version": 2,
                },
            )

        assert response.status_code == 201, response.content
        assert DiscoverSavedQuery.objects.filter(name="with team query").exists()

    def test_save_with_wrong_projects(self):
        other_org = self.create_organization(owner=self.user)
        project = self.create_project(organization=other_org)
        project2 = self.create_project(organization=self.org)
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": [project.id],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": f"project:{project.slug}",
                    "version": 2,
                },
            )
        assert response.status_code == 403, response.content
        assert not DiscoverSavedQuery.objects.filter(name="project query").exists()

        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": [project.id, project2.id],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": f"project:{project.slug} project:{project2.slug}",
                    "version": 2,
                },
            )
        assert response.status_code == 403, response.content
        assert not DiscoverSavedQuery.objects.filter(name="project query").exists()

        # Mix of wrong + valid
        with self.feature(self.feature_name):
            url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
            response = self.client.post(
                url,
                {
                    "name": "project query",
                    "projects": [-1],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": f"project:{project.slug} project:{project2.slug}",
                    "version": 2,
                },
            )
        assert response.status_code == 400, response.content
        assert not DiscoverSavedQuery.objects.filter(name="project query").exists()

    def test_save_with_equation(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "Equation query",
                    "projects": [-1],
                    "fields": [
                        "title",
                        "equation|count_if(measurements.lcp,greater,4000) / count()",
                        "count()",
                        "count_if(measurements.lcp,greater,4000)",
                    ],
                    "orderby": "equation[0]",
                    "range": "24h",
                    "query": "title:1",
                    "version": 2,
                },
            )
        assert response.status_code == 201, response.content
        assert DiscoverSavedQuery.objects.filter(name="Equation query").exists()

    def test_save_with_invalid_equation(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "Equation query",
                    "projects": [-1],
                    "fields": [
                        "title",
                        "equation|count_if(measurements.lcp,greater,4000) / 0",
                        "count()",
                        "count_if(measurements.lcp,greater,4000)",
                    ],
                    "orderby": "equation[0]",
                    "range": "24h",
                    "query": "title:1",
                    "version": 2,
                },
            )
        assert response.status_code == 400, response.content
        assert not DiscoverSavedQuery.objects.filter(name="Equation query").exists()

    def test_save_invalid_query(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "Bad query",
                    "projects": [-1],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": "spaceAfterColon: 1",
                    "version": 2,
                },
            )
        assert response.status_code == 400, response.content
        assert not DiscoverSavedQuery.objects.filter(name="Bad query").exists()

    def test_save_invalid_query_orderby(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "Bad query",
                    "projects": [-1],
                    "fields": ["title", "count()"],
                    "orderby": "fake()",
                    "range": "24h",
                    "query": "title:1",
                    "version": 2,
                },
            )
        assert response.status_code == 400, response.content
        assert not DiscoverSavedQuery.objects.filter(name="Bad query").exists()

    def test_save_interval(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "Interval query",
                    "projects": [-1],
                    "fields": ["title", "count()"],
                    "statsPeriod": "24h",
                    "query": "spaceAfterColon:1",
                    "version": 2,
                    "interval": "1m",
                },
            )
        assert response.status_code == 201, response.content
        assert response.data["name"] == "Interval query"
        assert response.data["interval"] == "1m"

    def test_save_invalid_interval(self):
        with self.feature(self.feature_name):
            response = self.client.post(
                self.url,
                {
                    "name": "Interval query",
                    "projects": [-1],
                    "fields": ["title", "count()"],
                    "range": "24h",
                    "query": "spaceAfterColon:1",
                    "version": 2,
                    "interval": "1s",
                },
            )
        assert response.status_code == 400, response.content
