import os
import shutil
import uuid

from django.urls import reverse
from model_bakery import baker

from apps.issue_events.constants import EventStatus, LogLevel
from apps.issue_events.models import Issue, IssueEvent, IssueHash
from apps.projects.models import IssueEventProjectHourlyStatistic
from apps.releases.models import Release

from ..process_event import process_issue_events
from ..schema import (
    CSPIssueEventSchema,
    ErrorIssueEventSchema,
    InterchangeIssueEvent,
    IssueEventSchema,
    SecuritySchema,
)
from .utils import EventIngestTestCase

COMPAT_TEST_DATA_DIR = "events/test_data"


def is_exception(v):
    return v.get("type") == "exception"


class IssueEventIngestTestCase(EventIngestTestCase):
    """
    These tests bypass the API and celery. They test the event ingest logic itself.
    This file should be large are test the following use cases
    - Multiple event saved at the same time
    - Sentry API compatibility
    - Default, Error, and CSP types
    - Graceful failure such as duplicate event ids or invalid data
    """

    def test_two_events(self):
        with self.assertNumQueries(7):
            self.process_events([{}, {}])
        self.assertEqual(Issue.objects.count(), 1)
        self.assertEqual(IssueHash.objects.count(), 1)
        self.assertEqual(IssueEvent.objects.count(), 2)
        self.assertTrue(
            IssueEventProjectHourlyStatistic.objects.filter(
                count=2, project=self.project
            ).exists()
        )

    def test_two_issues(self):
        self.process_events(
            [
                {
                    "message": "a",
                },
                {
                    "message": "b",
                },
            ]
        )
        self.assertEqual(Issue.objects.count(), 2)
        self.assertEqual(IssueHash.objects.count(), 2)
        self.assertEqual(IssueEvent.objects.count(), 2)
        self.assertTrue(
            IssueEventProjectHourlyStatistic.objects.filter(
                count=2, project=self.project
            ).exists()
        )

    def test_transaction_truncation(self):
        long_string = "x" * 201
        truncated_string = "x" * 199 + "…"

        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["culprit"] = long_string
        self.process_events(data)
        first_event = IssueEvent.objects.first()
        self.assertEqual(first_event.transaction, truncated_string)

        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["transaction"] = long_string
        self.process_events(data)
        second_event = IssueEvent.objects.last()
        self.assertEqual(second_event.transaction, truncated_string)

    def test_message_empty_param_list(self):
        self.process_events(
            [
                {"logentry": {"message": "This is a warning: %s", "params": []}},
            ]
        )
        self.assertEqual(
            IssueEvent.objects.first().data["logentry"]["message"],
            "This is a warning: %s",
        )

    def test_query_release_environment_difs(self):
        """Test efficiency of existing release/environment/dif"""
        project2 = baker.make("projects.Project", organization=self.organization)
        release = baker.make("releases.Release", version="r", projects=[self.project])
        environment = baker.make(
            "environments.Environment", name="e", projects=[self.project]
        )
        baker.make("difs.DebugInformationFile", project=self.project)
        baker.make("releases.Release", projects=[self.project, project2])
        baker.make("releases.Release", version="r", projects=[project2])
        baker.make("releases.Release", version="r")
        baker.make("environments.Environment", projects=[self.project])
        baker.make("difs.DebugInformationFile", project=self.project)
        event1 = {
            "release": release.version,
            "environment": environment.name,
        }
        event2 = {
            "release": "newr",
            "environment": "newe",
        }
        with self.assertNumQueries(13):
            self.process_events([event1, {}])
        self.process_events([event1, event2, {}])
        self.assertEqual(self.project.releases.count(), 3)
        self.assertEqual(self.project.environment_set.count(), 3)

    def test_reopen_resolved_issue(self):
        event = self.process_events({})[0]
        issue = Issue.objects.first()
        issue.status = EventStatus.RESOLVED
        issue.save()
        event.event_id = uuid.uuid4()
        self.process_events(event.dict())
        issue.refresh_from_db()
        self.assertEqual(issue.status, EventStatus.UNRESOLVED)

    def test_fingerprint(self):
        data = {
            "exception": [
                {
                    "type": "a",
                    "value": "a",
                }
            ],
            "event_id": uuid.uuid4(),
            "fingerprint": ["foo"],
        }
        self.process_events(data)

        data["exception"][0]["type"] = "lol"
        data["event_id"] = uuid.uuid4()
        self.process_events(data)
        self.assertEqual(Issue.objects.count(), 1)
        self.assertEqual(IssueEvent.objects.count(), 2)

    def test_event_release(self):
        data = self.get_json_data("events/test_data/py_hi_event.json")

        baker.make("releases.Release", version=data.get("release"))

        self.process_events(data)

        event = IssueEvent.objects.first()
        self.assertTrue(event.release)
        self.assertTrue(
            Release.objects.filter(
                version=data.get("release"), projects=self.project
            ).exists()
        )

    def test_event_release_blank(self):
        """In the SDK, it's possible to set a release to a blank string"""
        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["release"] = ""
        self.process_events(data)
        self.assertTrue(IssueEvent.objects.first())

    def test_event_environment(self):
        # Some noise to test queries
        baker.make("environments.Environment", organization=self.organization)
        baker.make("environments.EnvironmentProject", project=self.project)

        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["environment"] = "dev"
        self.process_events(data)

        event = IssueEvent.objects.first()
        self.assertTrue(event.issue.project.environment_set.filter(name="dev").exists())
        self.assertEqual(event.issue.project.environment_set.count(), 2)

        data["event_id"] = uuid.uuid4().hex
        self.process_events(data)
        self.assertEqual(event.issue.project.environment_set.count(), 2)

    def test_multi_org_event_environment_processing(self):
        environment = baker.make(
            "environments.Environment", organization=self.organization, name="prod"
        )
        baker.make(
            "environments.EnvironmentProject",
            environment=environment,
            project=self.project,
        )

        event_list = []
        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["environment"] = "dev"
        event_list.append(
            InterchangeIssueEvent(
                project_id=self.project.id,
                organization_id=self.organization.id,
                payload=IssueEventSchema(**data),
            )
        )

        org_b = baker.make("organizations_ext.organization")
        org_b_project = baker.make("projects.Project", organization=org_b)

        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["environment"] = "prod"
        event_list.append(
            InterchangeIssueEvent(
                project_id=org_b_project.id,
                organization_id=org_b.id,
                payload=IssueEventSchema(**data),
            )
        )

        process_issue_events(event_list)

        self.assertTrue(self.project.environment_set.filter(name="dev").exists())
        self.assertEqual(self.project.environment_set.count(), 2)

        self.assertTrue(org_b_project.environment_set.filter(name="prod").exists())
        self.assertEqual(org_b_project.environment_set.count(), 1)

    def test_multi_org_event_release_processing(self):
        release = baker.make(
            "releases.Release", organization=self.organization, version="v1.0"
        )
        baker.make(
            "releases.ReleaseProject",
            release=release,
            project=self.project,
        )

        event_list = []
        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["release"] = "v2.0"
        event_list.append(
            InterchangeIssueEvent(
                project_id=self.project.id,
                organization_id=self.organization.id,
                payload=IssueEventSchema(**data),
            )
        )

        org_b = baker.make("organizations_ext.organization")
        org_b_project = baker.make("projects.Project", organization=org_b)

        data = self.get_json_data("events/test_data/py_hi_event.json")
        data["release"] = "v1.0"
        event_list.append(
            InterchangeIssueEvent(
                project_id=org_b_project.id,
                organization_id=org_b.id,
                payload=IssueEventSchema(**data),
            )
        )

        process_issue_events(event_list)

        self.assertTrue(self.organization.release_set.filter(version="v2.0").exists())
        self.assertEqual(self.organization.release_set.count(), 2)

        self.assertTrue(org_b.release_set.filter(version="v1.0").exists())
        self.assertEqual(org_b.release_set.count(), 1)

    def test_process_sourcemap(self):
        sample_event = {
            "exception": {
                "values": [
                    {
                        "type": "Error",
                        "value": "The error",
                        "stacktrace": {
                            "frames": [
                                {
                                    "filename": "http://localhost:8080/dist/bundle.js",
                                    "function": "?",
                                    "in_app": True,
                                    "lineno": 2,
                                    "colno": 74016,
                                },
                                {
                                    "filename": "http://localhost:8080/dist/bundle.js",
                                    "function": "?",
                                    "in_app": True,
                                    "lineno": 2,
                                    "colno": 74012,
                                },
                                {
                                    "filename": "http://localhost:8080/dist/bundle.js",
                                    "function": "?",
                                    "in_app": True,
                                    "lineno": 2,
                                    "colno": 73992,
                                },
                            ]
                        },
                        "mechanism": {"type": "onerror", "handled": False},
                    }
                ]
            },
            "level": "error",
            "platform": "javascript",
            "event_id": "0691751a89db419994efac8ac9b00a5d",
            "timestamp": 1648414309.82,
            "environment": "production",
            "request": {
                "url": "http://localhost:8080/",
                "headers": {
                    "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0"
                },
            },
        }
        release = baker.make("releases.Release", organization=self.organization)
        release.projects.add(self.project)
        blob_bundle = baker.make("files.FileBlob", blob="uploads/file_blobs/bundle.js")
        blob_bundle_map = baker.make(
            "files.FileBlob", blob="uploads/file_blobs/bundle.js.map"
        )
        baker.make(
            "releases.ReleaseFile",
            release=release,
            file__name="bundle.js",
            file__blob=blob_bundle,
        )
        baker.make(
            "releases.ReleaseFile",
            release=release,
            file__name="bundle.js.map",
            file__blob=blob_bundle_map,
        )
        try:
            os.mkdir("./uploads/file_blobs")
        except FileExistsError:
            pass
        shutil.copyfile(
            "./apps/event_ingest/tests/test_data/bundle.js",
            "./uploads/file_blobs/bundle.js",
        )
        shutil.copyfile(
            "./apps/event_ingest/tests/test_data/bundle.js.map",
            "./uploads/file_blobs/bundle.js.map",
        )
        data = sample_event | {"release": release.version}

        self.process_events(data)
        # Show that colno changes
        self.assertEqual(
            IssueEvent.objects.first().data["exception"][0]["stacktrace"]["frames"][0][
                "colno"
            ],
            13,
        )
        # Show that pre and post context is included
        self.assertEqual(
            len(IssueEvent.objects.first().data["exception"][0]["stacktrace"]["frames"][0]["pre_context"]),
            5
        )
        self.assertEqual(
            len(IssueEvent.objects.first().data["exception"][0]["stacktrace"]["frames"][0]["post_context"]),
            1
        )

        self.assertTrue(IssueEvent.objects.filter(release=release).exists())

    def test_search_vector(self):
        word = "orange"
        for _ in range(2):
            self.process_events([{"message": word}])
        issue = Issue.objects.filter(search_vector=word).first()
        self.assertTrue(issue)
        self.assertEqual(len(issue.search_vector.split(" ")), 1)

    def test_null_character_event(self):
        """
        Unicode null characters \u0000 are not supported by Postgres JSONB
        NUL \x00 characters are not supported by Postgres string types
        They should be filtered out
        """
        data = self.get_json_data("events/test_data/py_error.json")
        data["exception"]["values"][0]["stacktrace"]["frames"][0]["function"] = (
            "a\u0000a"
        )
        data["exception"]["values"][0]["value"] = "\x00\u0000"
        self.process_events(data)

    def test_csp_event_processing(self):
        self.create_logged_in_user()
        payload = self.get_json_data(
            "apps/event_ingest/tests/test_data/csp/mozilla_example.json"
        )
        data = SecuritySchema(**payload)
        event = CSPIssueEventSchema(csp=data.csp_report.dict(by_alias=True))
        process_issue_events(
            [
                InterchangeIssueEvent(
                    project_id=self.project.id,
                    organization_id=self.organization.id,
                    payload=event.dict(by_alias=True),
                )
            ]
        )
        issue = Issue.objects.get()
        url = reverse("api:get_latest_issue_event", kwargs={"issue_id": issue.id})
        res = self.client.get(url)
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.json()["culprit"], "style-src-elem")


class SentryCompatTestCase(EventIngestTestCase):
    """
    These tests specifically test former open source sentry compatibility
    But otherwise are part of issue event ingest testing
    """

    def setUp(self):
        super().setUp()
        self.create_logged_in_user()

    def get_json_test_data(self, name: str):
        """Get incoming event, sentry json, sentry api event"""
        event = self.get_json_data(
            f"{COMPAT_TEST_DATA_DIR}/incoming_events/{name}.json"
        )
        sentry_json = self.get_json_data(
            f"{COMPAT_TEST_DATA_DIR}/oss_sentry_json/{name}.json"
        )
        # Force captured test data to match test generated data
        sentry_json["project"] = self.project.id
        api_sentry_event = self.get_json_data(
            f"{COMPAT_TEST_DATA_DIR}/oss_sentry_events/{name}.json"
        )
        return event, sentry_json, api_sentry_event

    def get_event_json(self, event: IssueEvent):
        return self.client.get(
            reverse(
                "api:get_event_json",
                kwargs={
                    "organization_slug": self.organization.slug,
                    "issue_id": event.issue_id,
                    "event_id": event.id,
                },
            )
        ).json()

    # Upgrade functions handle intentional differences between GlitchTip and Sentry OSS
    def upgrade_title(self, value: str):
        """Sentry OSS uses ... while GlitchTip uses unicode …"""
        if value[-1] == "…":
            return value[:-3]
        return value.strip("...")

    def upgrade_metadata(self, value: dict):
        value["title"] = self.upgrade_title(value["title"])
        return value

    def assertCompareData(self, data1: dict, data2: dict, fields: list[str]):
        """Compare data of two dict objects. Compare only provided fields list"""
        for field in fields:
            field_value1 = data1.get(field)
            field_value2 = data2.get(field)
            if field == "datetime":
                # Check that it's close enough
                field_value1 = field_value1[:23]
                field_value2 = field_value2[:23]
            if field == "title" and isinstance(field_value1, str):
                field_value1 = self.upgrade_title(field_value1)
                if field_value2:
                    field_value2 = self.upgrade_title(field_value2)
            if (
                field == "metadata"
                and isinstance(field_value1, dict)
                and field_value1.get("title")
            ):
                field_value1 = self.upgrade_metadata(field_value1)
                if field_value2:
                    field_value2 = self.upgrade_metadata(field_value2)
            self.assertEqual(
                field_value1,
                field_value2,
                f"Failed for field '{field}'",
            )

    def get_project_events_detail(self, event_id: str):
        return reverse(
            "api:get_project_issue_event",
            kwargs={
                "organization_slug": self.project.organization.slug,
                "project_slug": self.project.slug,
                "event_id": event_id,
            },
        )

    def submit_event(self, event_data: dict, event_type="error") -> IssueEvent:
        event_class = ErrorIssueEventSchema

        if event_type == "default":
            event_class = IssueEventSchema
        event = InterchangeIssueEvent(
            event_id=event_data["event_id"],
            organization_id=self.organization.id if self.organization else None,
            project_id=self.project.id,
            payload=event_class(**event_data),
        )
        process_issue_events([event])
        return IssueEvent.objects.get(pk=event.event_id)

    def upgrade_data(self, data):
        """A recursive replace function"""
        if isinstance(data, dict):
            return {k: self.upgrade_data(v) for k, v in data.items()}
        elif isinstance(data, list):
            return [self.upgrade_data(i) for i in data]
        return data

    def test_template_error(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "django_template_error"
        )
        event = self.submit_event(sdk_error)

        url = self.get_project_events_detail(event.id.hex)
        res = self.client.get(url)
        res_data = res.json()
        self.assertEqual(res.status_code, 200)
        self.assertCompareData(res_data, sentry_data, ["culprit", "title", "metadata"])
        res_frames = res_data["entries"][0]["data"]["values"][0]["stacktrace"]["frames"]
        frames = sentry_data["entries"][0]["data"]["values"][0]["stacktrace"]["frames"]

        for i in range(6):
            # absPath don't always match - needs fixed
            self.assertCompareData(res_frames[i], frames[i], ["absPath"])
        for res_frame, frame in zip(res_frames, frames):
            self.assertCompareData(
                res_frame,
                frame,
                ["lineNo", "function", "filename", "module", "context"],
            )
            if frame.get("vars"):
                self.assertCompareData(
                    res_frame["vars"], frame["vars"], ["exc", "request"]
                )
                if frame["vars"].get("get_response"):
                    # Memory address is different, truncate it
                    self.assertEqual(
                        res_frame["vars"]["get_response"][:-16],
                        frame["vars"]["get_response"][:-16],
                    )

        self.assertCompareData(
            res_data["entries"][0]["data"],
            sentry_data["entries"][0]["data"],
            ["env", "headers", "url", "method", "inferredContentType"],
        )

        url = reverse("api:get_issue", kwargs={"issue_id": event.issue.pk})
        res = self.client.get(url)
        self.assertEqual(res.status_code, 200)
        res_data = res.json()

        data = self.get_json_data("events/test_data/django_template_error_issue.json")
        self.assertCompareData(res_data, data, ["title", "metadata"])

    def test_js_sdk_with_unix_timestamp(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "js_event_with_unix_timestamp"
        )
        event = self.submit_event(sdk_error)
        self.assertNotEqual(event.timestamp, sdk_error["timestamp"])
        self.assertEqual(event.timestamp.year, 2020)

        event_json = self.get_event_json(event)
        self.assertCompareData(event_json, sentry_json, ["datetime"])

        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()
        self.assertCompareData(res_data, sentry_data, ["timestamp"])
        self.assertEqual(res_data["entries"][1].get("type"), "breadcrumbs")
        self.maxDiff = None
        self.assertEqual(
            res_data["entries"][1],
            self.upgrade_data(sentry_data["entries"][1]),
        )

    def test_dotnet_error(self):
        sdk_error = self.get_json_data(
            "events/test_data/incoming_events/dotnet_error.json"
        )
        event = self.submit_event(sdk_error)
        self.assertEqual(IssueEvent.objects.count(), 1)

        sentry_data = self.get_json_data(
            "events/test_data/oss_sentry_events/dotnet_error.json"
        )
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()
        self.assertCompareData(
            res_data,
            sentry_data,
            ["eventID", "title", "culprit", "platform", "type", "metadata"],
        )
        res_exception = next(filter(is_exception, res_data["entries"]), None)
        sentry_exception = next(filter(is_exception, sentry_data["entries"]), None)
        self.assertEqual(
            res_exception["data"].get("hasSystemFrames"),
            sentry_exception["data"].get("hasSystemFrames"),
        )

    def test_php_message_event(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "php_message_event"
        )
        event = self.submit_event(sdk_error)

        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()

        self.assertCompareData(
            res_data,
            sentry_data,
            [
                "message",
                "title",
            ],
        )
        self.assertEqual(
            res_data["entries"][0]["data"]["params"],
            sentry_data["entries"][0]["data"]["params"],
        )

    def test_django_message_params(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "django_message_params"
        )
        event = self.submit_event(sdk_error)
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()

        self.assertCompareData(
            res_data,
            sentry_data,
            [
                "message",
                "title",
            ],
        )
        self.assertEqual(res_data["entries"][0], sentry_data["entries"][0])

    def test_message_event(self):
        """A generic message made with the Sentry SDK. Generally has less data than exceptions."""
        from events.test_data.django_error_factory import message

        event = self.submit_event(message, event_type="default")
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()

        data = self.get_json_data("events/test_data/django_message_event.json")
        self.assertCompareData(
            res_data,
            data,
            ["title", "culprit", "type", "metadata", "platform", "packages"],
        )

    def test_python_logging(self):
        """Test Sentry SDK logging integration based event"""
        sdk_error, sentry_json, sentry_data = self.get_json_test_data("python_logging")
        event = self.submit_event(sdk_error, event_type="default")

        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()

        self.assertEqual(res.status_code, 200)
        self.assertCompareData(
            res_data,
            sentry_data,
            [
                "title",
                "logentry",
                "culprit",
                "type",
                "metadata",
                "platform",
                "packages",
            ],
        )

    def test_go_file_not_found(self):
        sdk_error = self.get_json_data(
            "events/test_data/incoming_events/go_file_not_found.json"
        )
        event = self.submit_event(sdk_error)

        sentry_data = self.get_json_data(
            "events/test_data/oss_sentry_events/go_file_not_found.json"
        )
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()
        self.assertEqual(res.status_code, 200)
        self.assertCompareData(
            res_data,
            sentry_data,
            ["title", "culprit", "type", "metadata", "platform"],
        )

    def test_very_small_event(self):
        """
        Shows a very minimalist event example. Good for seeing what data is null
        """
        sdk_error = self.get_json_data(
            "events/test_data/incoming_events/very_small_event.json"
        )
        event = self.submit_event(sdk_error, event_type="default")

        sentry_data = self.get_json_data(
            "events/test_data/oss_sentry_events/very_small_event.json"
        )
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()
        self.assertEqual(res.status_code, 200)
        self.assertCompareData(
            res_data,
            sentry_data,
            ["culprit", "type", "platform", "entries"],
        )

    def test_python_zero_division(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "python_zero_division"
        )
        event = self.submit_event(sdk_error)
        event_json = self.get_event_json(event)
        self.assertCompareData(
            event_json,
            sentry_json,
            [
                "event_id",
                "project",
                "release",
                "dist",
                "platform",
                "level",
                "modules",
                "time_spent",
                "sdk",
                "type",
                "title",
                "breadcrumbs",
            ],
        )
        self.assertCompareData(
            event_json["request"],
            sentry_json["request"],
            [
                "url",
                "headers",
                "method",
                "env",
                "query_string",
            ],
        )
        self.assertEqual(
            event_json["datetime"][:22],
            sentry_json["datetime"][:22],
            "Compare if datetime is almost the same",
        )

        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()
        self.assertEqual(res.status_code, 200)
        self.assertCompareData(
            res_data,
            sentry_data,
            ["title", "culprit", "type", "metadata", "platform", "packages"],
        )
        self.assertCompareData(
            res_data["entries"][1]["data"],
            sentry_data["entries"][1]["data"],
            [
                "inferredContentType",
                "env",
                "headers",
                "url",
                "query",
                "data",
                "method",
            ],
        )
        issue = event.issue
        issue.refresh_from_db()
        self.assertEqual(issue.level, LogLevel.ERROR)

    def test_dotnet_zero_division(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "dotnet_divide_zero"
        )
        event = self.submit_event(sdk_error)
        event_json = self.get_event_json(event)
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()

        self.assertCompareData(event_json, sentry_json, ["environment"])
        self.assertCompareData(
            res_data,
            sentry_data,
            [
                "eventID",
                "title",
                "culprit",
                "platform",
                "type",
                "metadata",
            ],
        )
        res_exception = next(filter(is_exception, res_data["entries"]), None)
        sentry_exception = next(filter(is_exception, sentry_data["entries"]), None)
        self.assertEqual(
            res_exception["data"]["values"][0]["stacktrace"]["frames"][4]["context"],
            sentry_exception["data"]["values"][0]["stacktrace"]["frames"][4]["context"],
        )
        tags = res_data.get("tags")
        browser_tag = next(filter(lambda tag: tag["key"] == "browser", tags), None)
        self.assertEqual(browser_tag["value"], "Firefox 76.0")
        environment_tag = next(
            filter(lambda tag: tag["key"] == "environment", tags), None
        )
        self.assertEqual(environment_tag["value"], "Development")

    def test_ruby_zero_division(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "ruby_zero_division"
        )

        event = self.submit_event(sdk_error)
        event_json = self.get_event_json(event)
        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()
        res_exception = next(filter(is_exception, res_data["entries"]), None)
        sentry_exception = next(filter(is_exception, sentry_data["entries"]), None)
        self.assertEqual(
            res_exception["data"]["values"][0]["stacktrace"]["frames"][-1]["context"],
            sentry_exception["data"]["values"][0]["stacktrace"]["frames"][-1]["context"],
        )

        self.assertCompareData(event_json, sentry_json, ["environment"])
        self.assertCompareData(
            res_data,
            sentry_data,
            [
                "eventID",
                "title",
                "culprit",
                "platform",
                "type",
                "metadata",
            ],
        )

    def test_sentry_cli_send_event_no_level(self):
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "sentry_cli_send_event_no_level"
        )
        event = self.submit_event(sdk_error, event_type="default")
        event_json = self.get_event_json(event)

        self.assertCompareData(event_json, sentry_json, ["title"])
        self.assertEqual(event_json["project"], event.issue.project_id)

        res = self.client.get(self.get_project_events_detail(event.pk))
        res_data = res.json()

        self.assertCompareData(
            res_data,
            sentry_data,
            [
                "userReport",
                "title",
                "culprit",
                "type",
                "metadata",
                "message",
                "platform",
                "previousEventID",
            ],
        )
        self.assertEqual(res_data["projectID"], event.issue.project_id)

    def test_js_error_with_context(self):
        self.project.scrub_ip_addresses = False
        self.project.save()
        sdk_error, sentry_json, sentry_data = self.get_json_test_data(
            "js_error_with_context"
        )
        event_store_url = (
            reverse("api:event_store", args=[self.project.id])
            + "?sentry_key="
            + self.project.projectkey_set.first().public_key.hex
        )
        res = self.client.post(
            event_store_url,
            sdk_error,
            content_type="application/json",
            REMOTE_ADDR="142.255.29.14",
        )
        res_data = res.json()
        event = IssueEvent.objects.get(pk=res_data["event_id"])
        event_json = self.get_event_json(event)
        self.assertCompareData(event_json, sentry_json, ["title", "extra", "user"])

        url = self.get_project_events_detail(event.pk)
        res = self.client.get(url)
        res_json = res.json()
        self.assertCompareData(res_json, sentry_data, ["context"])
        self.assertCompareData(
            res_json["user"], sentry_data["user"], ["id", "email", "ip_address"]
        )

    def test_small_js_error(self):
        """A small example to test stacktraces"""
        sdk_error, sentry_json, sentry_data = self.get_json_test_data("small_js_error")
        event = self.submit_event(sdk_error, event_type="default")
        event_json = self.get_event_json(event)
        self.assertCompareData(
            event_json["exception"][0],
            sentry_json["exception"]["values"][0],
            ["type", "values", "exception", "abs_path"],
        )