import logging

from django.db import models
from django.utils import timezone

from bitfield import TypedClassBitField
from sentry.db.models import (
    BoundedBigIntegerField,
    BoundedPositiveIntegerField,
    Model,
    control_silo_only_model,
    sane_repr,
)
from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey
from sentry.db.models.fields.jsonfield import JSONField
from sentry.services.hybrid_cloud.organization import organization_service

logger = logging.getLogger("sentry.authprovider")

SCIM_INTERNAL_INTEGRATION_OVERVIEW = (
    "This internal integration was auto-generated during the installation process of your SCIM "
    "integration. It is needed to provide the token used to provision members and teams. If this integration is "
    "deleted, your SCIM integration will stop working!"
)


@control_silo_only_model
class AuthProviderDefaultTeams(Model):
    # Completely defunct model.
    __include_in_export__ = False

    authprovider_id = BoundedBigIntegerField()
    team_id = BoundedBigIntegerField()

    class Meta:
        app_label = "sentry"
        db_table = "sentry_authprovider_default_teams"
        unique_together = tuple()


@control_silo_only_model
class AuthProvider(Model):
    __include_in_export__ = True

    organization_id = HybridCloudForeignKey("sentry.Organization", on_delete="cascade", unique=True)
    provider = models.CharField(max_length=128)
    config = JSONField()

    date_added = models.DateTimeField(default=timezone.now)
    sync_time = BoundedPositiveIntegerField(null=True)
    last_sync = models.DateTimeField(null=True)

    default_role = BoundedPositiveIntegerField(default=50)
    default_global_access = models.BooleanField(default=True)

    class flags(TypedClassBitField):
        # Grant access to members who have not linked SSO accounts.
        allow_unlinked: bool
        # Enable SCIM for member and team provisioning and syncing.
        scim_enabled: bool

        bitfield_default = 0

    class Meta:
        app_label = "sentry"
        db_table = "sentry_authprovider"

    __repr__ = sane_repr("organization_id", "provider")

    def __str__(self):
        return self.provider

    def get_provider(self):
        from sentry.auth import manager

        return manager.get(self.provider, **self.config)

    @property
    def provider_name(self) -> str:
        return self.get_provider().name

    def get_scim_token(self):
        from sentry.models import SentryAppInstallationToken

        if self.flags.scim_enabled:
            return SentryAppInstallationToken.objects.get_token(
                self.organization_id, f"{self.provider}_scim"
            )
        else:
            logger.warning(
                "SCIM disabled but tried to access token",
                extra={"organization_id": self.organization_id},
            )
            return None

    def enable_scim(self, user):
        from sentry.models import SentryAppInstallation, SentryAppInstallationForProvider
        from sentry.sentry_apps.apps import SentryAppCreator

        if (
            not self.get_provider().can_use_scim(self.organization_id, user)
            or self.flags.scim_enabled is True
        ):
            logger.warning(
                "SCIM already enabled",
                extra={"organization_id": self.organization_id},
            )
            return

        # check if we have a scim app already

        if SentryAppInstallationForProvider.objects.filter(
            organization_id=self.organization_id, provider="okta_scim"
        ).exists():
            logger.warning(
                "SCIM installation already exists",
                extra={"organization_id": self.organization_id},
            )
            return

        sentry_app = SentryAppCreator(
            name="SCIM Internal Integration",
            author="Auto-generated by Sentry",
            organization_id=self.organization_id,
            overview=SCIM_INTERNAL_INTEGRATION_OVERVIEW,
            is_internal=True,
            verify_install=False,
            scopes=[
                "member:read",
                "member:write",
                "member:admin",
                "team:write",
                "team:admin",
            ],
        ).run(user=user)
        sentry_app_installation = SentryAppInstallation.objects.get(sentry_app=sentry_app)
        SentryAppInstallationForProvider.objects.create(
            sentry_app_installation=sentry_app_installation,
            organization_id=self.organization_id,
            provider=f"{self.provider}_scim",
        )
        self.flags.scim_enabled = True

    def disable_scim(self, user):
        from sentry import deletions
        from sentry.models import SentryAppInstallationForProvider

        if self.flags.scim_enabled:
            install = SentryAppInstallationForProvider.objects.get(
                organization_id=self.organization_id, provider=f"{self.provider}_scim"
            )
            # Only one SCIM installation allowed per organization. So we can reset the idp flags for the orgs
            # We run this update before the app is uninstalled to avoid ending up in a situation where there are
            # members locked out because we failed to drop the IDP flag
            organization_service.reset_idp_flags(organization_id=self.organization_id)
            sentry_app = install.sentry_app_installation.sentry_app
            assert (
                sentry_app.is_internal
            ), "scim sentry apps should always be internal, thus deleting them without triggering InstallationNotifier is correct."
            deletions.exec_sync(sentry_app)
            self.flags.scim_enabled = False

    def get_audit_log_data(self):
        return {"provider": self.provider, "config": self.config}
