from datetime import timedelta

import pytest
from django.urls import reverse

from sentry.models.transaction_threshold import ProjectTransactionThreshold, TransactionMetric
from sentry.testutils.cases import APITestCase, MetricsEnhancedPerformanceTestCase, SnubaTestCase
from sentry.testutils.helpers.datetime import before_now, iso_format
from sentry.testutils.silo import region_silo_test
from sentry.utils.samples import load_data

pytestmark = pytest.mark.sentry_metrics


@region_silo_test
class OrganizationEventsVitalsEndpointTest(APITestCase, SnubaTestCase):
    def setUp(self):
        super().setUp()
        self.start = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
        self.end = self.start + timedelta(hours=6)

        self.transaction_data = load_data("transaction", timestamp=self.start)
        self.query = {
            "start": iso_format(self.start),
            "end": iso_format(self.end),
        }
        self.features = {}

    def store_event(self, data, measurements=None, **kwargs):
        if measurements:
            for vital, value in measurements.items():
                data["measurements"][vital]["value"] = value

        return super().store_event(
            data.copy(),
            project_id=self.project.id,
        )

    def do_request(self, query=None, features=None):
        if features is None:
            features = {"organizations:discover-basic": True}
        features.update(self.features)
        if query is None:
            query = self.query

        self.login_as(user=self.user)
        url = reverse(
            "sentry-api-0-organization-events-vitals",
            kwargs={"organization_slug": self.organization.slug},
        )

        with self.feature(features):
            return self.client.get(url, query, format="json")

    def test_no_projects(self):
        response = self.do_request()
        assert response.status_code == 200, response.content
        assert len(response.data) == 0

    def test_no_vitals(self):
        self.store_event(
            self.transaction_data,
            project_id=self.project.id,
        )

        self.query.update({"vital": []})
        response = self.do_request()
        assert response.status_code == 400, response.content
        assert "Need to pass at least one vital" == response.data["detail"]

    def test_bad_vital(self):
        self.store_event(
            self.transaction_data,
            project_id=self.project.id,
        )

        self.query.update({"vital": ["foobar"]})
        response = self.do_request()
        assert response.status_code == 400, response.content
        assert "foobar is not a valid vital" == response.data["detail"]

    def test_simple(self):
        data = self.transaction_data.copy()
        for lcp in [2000, 3000, 5000]:
            self.store_event(
                data,
                {"lcp": lcp},
                project_id=self.project.id,
            )

        self.query.update({"vital": ["measurements.lcp"]})
        response = self.do_request()
        assert response.status_code == 200, response.content
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 1,
            "meh": 1,
            "poor": 1,
            "total": 3,
            "p75": 4000,
        }

    def test_simple_with_refining_user_misery_filter(self):
        project1 = self.create_project(organization=self.organization)
        project2 = self.create_project(organization=self.organization)
        ProjectTransactionThreshold.objects.create(
            project=project1,
            organization=project1.organization,
            threshold=100,
            metric=TransactionMetric.LCP.value,
        )

        ProjectTransactionThreshold.objects.create(
            project=project2,
            organization=project2.organization,
            threshold=1000,
            metric=TransactionMetric.LCP.value,
        )

        data = self.transaction_data.copy()

        for project in [project1, project2]:
            for lcp in [2000, 3000, 5000]:
                self.store_event(
                    data,
                    {"lcp": lcp},
                    project_id=project.id,
                )

        self.query.update({"vital": ["measurements.lcp"]})
        response = self.do_request(
            features={"organizations:global-views": True, "organizations:discover-basic": True}
        )

        assert response.status_code == 200, response.content
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 1,
            "poor": 1,
            "total": 2,
            "p75": 4500,
        }

        self.query.update({"query": "user_misery():<0.04"})
        response = self.do_request(
            features={"organizations:global-views": True, "organizations:discover-basic": True}
        )

        assert response.status_code == 200, response.content
        assert len(response.data) == 2
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 1,
            "poor": 1,
            "total": 2,
            "p75": 4500,
        }

    def test_grouping(self):
        counts = [
            (100, 2),
            (3000, 3),
            (4500, 1),
        ]
        for duration, count in counts:
            for _ in range(count):
                self.store_event(
                    load_data("transaction", timestamp=self.start),
                    {"lcp": duration},
                    project_id=self.project.id,
                )

        self.query.update({"vital": ["measurements.lcp"]})
        response = self.do_request()
        assert response.status_code == 200
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 2,
            "meh": 3,
            "poor": 1,
            "total": 6,
            "p75": 3000,
        }

    def test_multiple_vitals(self):
        vitals = {"lcp": 3000, "fid": 50, "cls": 0.15, "fcp": 5000, "fp": 4000}
        self.store_event(
            load_data("transaction", timestamp=self.start),
            vitals,
            project_id=self.project.id,
        )

        self.query.update(
            {
                "vital": [
                    "measurements.lcp",
                    "measurements.fid",
                    "measurements.cls",
                    "measurements.fcp",
                    "measurements.fp",
                ]
            }
        )
        response = self.do_request()
        assert response.status_code == 200
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 1,
            "poor": 0,
            "total": 1,
            "p75": 3000,
        }
        assert response.data["measurements.fid"] == {
            "good": 1,
            "meh": 0,
            "poor": 0,
            "total": 1,
            "p75": 50,
        }
        assert response.data["measurements.cls"] == {
            "good": 0,
            "meh": 1,
            "poor": 0,
            "total": 1,
            "p75": 0.15,
        }
        assert response.data["measurements.fcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 1,
            "total": 1,
            "p75": 5000,
        }
        assert response.data["measurements.fp"] == {
            "good": 0,
            "meh": 0,
            "poor": 1,
            "total": 1,
            "p75": 4000,
        }

    def test_transactions_without_vitals(self):
        del self.transaction_data["measurements"]
        self.store_event(
            self.transaction_data,
            project_id=self.project.id,
        )

        self.query.update({"vital": ["measurements.lcp", "measurements.fcp"]})
        response = self.do_request()
        assert response.status_code == 200, response.data
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 0,
            "total": 0,
            "p75": None,
        }
        assert response.data["measurements.fcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 0,
            "total": 0,
            "p75": None,
        }

    def test_edges_of_vital_thresholds(self):
        self.store_event(
            load_data("transaction", timestamp=self.start),
            {"lcp": 4000, "fp": 1000, "fcp": 0},
            project_id=self.project.id,
        )

        self.query.update({"vital": ["measurements.lcp", "measurements.fp", "measurements.fcp"]})
        response = self.do_request()
        assert response.status_code == 200, response.data
        assert not response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 1,
            "total": 1,
            "p75": 4000,
        }
        assert response.data["measurements.fp"] == {
            "good": 0,
            "meh": 1,
            "poor": 0,
            "total": 1,
            "p75": 1000,
        }
        assert response.data["measurements.fcp"] == {
            "good": 1,
            "meh": 0,
            "poor": 0,
            "total": 1,
            "p75": 0,
        }


@region_silo_test
class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
    METRIC_STRINGS = ["measurement_rating"]

    def setUp(self):
        super().setUp()
        self.start = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
        self.end = self.start + timedelta(hours=6)

        self.query = {
            "start": iso_format(self.start),
            "end": iso_format(self.end),
        }
        self.features = {"organizations:performance-use-metrics": True}

    def do_request(self, query=None, features=None):
        if features is None:
            features = {"organizations:discover-basic": True}
        features.update(self.features)
        if query is None:
            query = self.query
        query["dataset"] = "metricsEnhanced"

        self.login_as(user=self.user)
        with self.feature(features):
            url = reverse(
                "sentry-api-0-organization-events-vitals",
                kwargs={"organization_slug": self.organization.slug},
            )

        with self.feature(features):
            return self.client.get(url, query, format="json")

    def test_no_projects(self):
        response = self.do_request()
        assert response.status_code == 200, response.content
        assert len(response.data) == 0

    def test_no_vitals(self):
        self.query.update({"vital": [], "project": self.project.id})
        response = self.do_request()
        assert response.status_code == 400, response.content
        assert "Need to pass at least one vital" == response.data["detail"]

    def test_simple(self):
        for rating, lcp in [("good", 2000), ("meh", 3000), ("poor", 5000)]:
            self.store_transaction_metric(
                lcp,
                metric="measurements.lcp",
                tags={"transaction": "foo_transaction", "measurement_rating": rating},
                timestamp=self.start + timedelta(minutes=5),
            )

        self.query.update({"vital": ["measurements.lcp"]})
        response = self.do_request()
        assert response.status_code == 200, response.content
        assert response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 1,
            "meh": 1,
            "poor": 1,
            "total": 3,
            "p75": 4000,
        }

    def test_grouping(self):
        counts = [
            ("good", 100, 2),
            ("meh", 3000, 3),
            ("poor", 4500, 1),
        ]
        for rating, duration, count in counts:
            for _ in range(count):
                self.store_transaction_metric(
                    duration,
                    metric="measurements.lcp",
                    tags={"transaction": "foo_transaction", "measurement_rating": rating},
                    timestamp=self.start + timedelta(minutes=5),
                )

        self.query.update({"vital": ["measurements.lcp"]})
        response = self.do_request()
        assert response.status_code == 200
        assert response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 2,
            "meh": 3,
            "poor": 1,
            "total": 6,
            "p75": 3000,
        }

    def test_multiple_vitals(self):
        vitals = [
            ("measurements.lcp", 3000, "meh"),
            ("measurements.fid", 50, "good"),
            ("measurements.cls", 0.15, "meh"),
            ("measurements.fcp", 5000, "poor"),
            ("measurements.fp", 4000, "poor"),
        ]
        for vital, duration, rating in vitals:
            self.store_transaction_metric(
                duration,
                metric=vital,
                tags={"transaction": "foo_transaction", "measurement_rating": rating},
                timestamp=self.start + timedelta(minutes=5),
            )

        self.query.update(
            {
                "vital": [
                    "measurements.lcp",
                    "measurements.fid",
                    "measurements.cls",
                    "measurements.fcp",
                    "measurements.fp",
                ]
            }
        )
        response = self.do_request()
        assert response.status_code == 200
        assert response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 1,
            "poor": 0,
            "total": 1,
            "p75": 3000,
        }
        assert response.data["measurements.fid"] == {
            "good": 1,
            "meh": 0,
            "poor": 0,
            "total": 1,
            "p75": 50,
        }
        assert response.data["measurements.cls"] == {
            "good": 0,
            "meh": 1,
            "poor": 0,
            "total": 1,
            "p75": 0.15,
        }
        assert response.data["measurements.fcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 1,
            "total": 1,
            "p75": 5000,
        }
        assert response.data["measurements.fp"] == {
            "good": 0,
            "meh": 0,
            "poor": 1,
            "total": 1,
            "p75": 4000,
        }

    def test_transactions_without_vitals(self):
        self.query.update(
            {"vital": ["measurements.lcp", "measurements.fcp"], "project": self.project.id}
        )
        response = self.do_request()
        assert response.status_code == 200, response.data
        assert response.data["meta"]["isMetricsData"]
        assert response.data["measurements.lcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 0,
            "total": 0,
            "p75": 0,
        }
        assert response.data["measurements.fcp"] == {
            "good": 0,
            "meh": 0,
            "poor": 0,
            "total": 0,
            "p75": 0,
        }