import uuid
from unittest import mock

import pytest
import sentry_sdk
from django.test.utils import override_settings
from sentry_sdk import Hub, push_scope

from sentry import eventstore
from sentry.eventstore.models import Event
from sentry.models.userrole import manage_default_super_admin_role
from sentry.receivers import create_default_projects
from sentry.silo.base import SiloMode
from sentry.testutils.asserts import assert_mock_called_once_with_partial
from sentry.testutils.pytest.fixtures import django_db_all
from sentry.testutils.pytest.relay import adjust_settings_for_relay_tests
from sentry.testutils.silo import assume_test_silo_mode, no_silo_test
from sentry.testutils.skips import requires_kafka
from sentry.utils.sdk import bind_organization_context, configure_sdk

pytestmark = [requires_kafka]


@pytest.fixture(autouse=True)
def setup_fixtures():
    with assume_test_silo_mode(SiloMode.CONTROL):
        manage_default_super_admin_role()
    create_default_projects()


@pytest.fixture
def post_event_with_sdk(settings, relay_server, wait_for_ingest_consumer):
    adjust_settings_for_relay_tests(settings)
    settings.SENTRY_ENDPOINT = relay_server["url"]
    settings.SENTRY_PROJECT = 1

    configure_sdk()

    current_scope = sentry_sdk.Scope()
    isolation_scope = sentry_sdk.Scope()

    wait_for_ingest_consumer = wait_for_ingest_consumer(settings)

    def inner(*args, **kwargs):
        event_id = sentry_sdk.capture_event(*args, **kwargs)
        assert event_id is not None
        sentry_sdk.flush()

        with sentry_sdk.scope.use_scope(current_scope):
            with sentry_sdk.scope.use_isolation_scope(isolation_scope):
                return wait_for_ingest_consumer(
                    lambda: eventstore.backend.get_event_by_id(settings.SENTRY_PROJECT, event_id)
                )

    yield inner


@no_silo_test
@override_settings(SENTRY_PROJECT=1)
@django_db_all
def test_simple(settings, post_event_with_sdk):
    event = post_event_with_sdk({"message": "internal client test"})

    assert event
    assert event.data["project"] == settings.SENTRY_PROJECT
    assert event.data["logentry"]["formatted"] == "internal client test"


@no_silo_test
@override_settings(SENTRY_PROJECT=1)
@django_db_all
def test_recursion_breaker(settings, post_event_with_sdk):
    # If this test terminates at all then we avoided recursion.
    settings.SENTRY_INGEST_CONSUMER_APM_SAMPLING = 1.0
    settings.SENTRY_PROJECT = 1

    event_id = uuid.uuid4().hex
    with mock.patch(
        "sentry.event_manager.EventManager.save", spec=Event, side_effect=ValueError("oh no!")
    ) as save:
        with pytest.raises(Exception):
            post_event_with_sdk({"message": "internal client test", "event_id": event_id})

    assert_mock_called_once_with_partial(save, settings.SENTRY_PROJECT, cache_key=f"e:{event_id}:1")


@no_silo_test
@django_db_all
@override_settings(SENTRY_PROJECT=1)
def test_encoding(settings, post_event_with_sdk):
    class NotJSONSerializable:
        pass

    with push_scope() as scope:
        scope.set_extra("request", NotJSONSerializable())
        event = post_event_with_sdk({"message": "check the req"})

    assert event.data["project"] == settings.SENTRY_PROJECT
    assert event.data["logentry"]["formatted"] == "check the req"
    assert "NotJSONSerializable" in event.data["extra"]["request"]


@no_silo_test
@override_settings(SENTRY_PROJECT=1)
@django_db_all
def test_bind_organization_context(default_organization):
    configure_sdk()

    bind_organization_context(default_organization)

    assert Hub.current.scope._tags["organization"] == default_organization.id
    assert Hub.current.scope._tags["organization.slug"] == default_organization.slug
    assert Hub.current.scope._contexts["organization"] == {
        "id": default_organization.id,
        "slug": default_organization.slug,
    }


@no_silo_test
@override_settings(SENTRY_PROJECT=1)
@django_db_all
def test_bind_organization_context_with_callback(default_organization):
    create_default_projects()
    configure_sdk()

    def add_context(scope, organization, **kwargs):
        scope.set_tag("organization.test", "1")

    with override_settings(SENTRY_ORGANIZATION_CONTEXT_HELPER=add_context):
        bind_organization_context(default_organization)
        assert Hub.current.scope._tags["organization.test"] == "1"


@no_silo_test
@override_settings(SENTRY_PROJECT=1)
@django_db_all
def test_bind_organization_context_with_callback_error(default_organization):
    configure_sdk()

    def add_context(scope, organization, **kwargs):
        1 / 0

    with override_settings(SENTRY_ORGANIZATION_CONTEXT_HELPER=add_context):
        bind_organization_context(default_organization)
        assert Hub.current.scope._tags["organization"] == default_organization.id