import string

from django.core.cache import cache
from django.http import Http404
from django.utils.crypto import get_random_string
from ninja import Field, Router, Schema
from ninja.errors import HttpError

from apps.api_tokens.models import APIToken
from apps.api_tokens.schema import APITokenSchema
from apps.projects.models import Project
from apps.projects.schema import ProjectWithKeysSchema
from glitchtip.api.authentication import AuthHttpRequest
from glitchtip.schema import CamelSchema

from .constants import (
    SETUP_WIZARD_CACHE_EMPTY,
    SETUP_WIZARD_CACHE_KEY,
    SETUP_WIZARD_CACHE_TIMEOUT,
)


class SetupWizardSchema(Schema):
    """
    A 64 char random string used to provide a shorted lived and secure
    way to transfer sensative data.
    """

    hash: str = Field(min_length=64, max_length=64)


class SetupWizardResultSchema(CamelSchema):
    """
    Payload containing projects data and api key that sentry-wizard could use
    to configure a local project for usage with GlitchTip
    """

    api_keys: APITokenSchema
    projects: list[ProjectWithKeysSchema]


router = Router()


@router.get("wizard/", response=SetupWizardSchema, auth=None)
def setup_wizard(request):
    """
    First step used by sentry-wizard
    Generates a random hash for later usage
    """
    wizard_hash = get_random_string(
        64, allowed_chars=string.ascii_lowercase + string.digits
    )
    key = SETUP_WIZARD_CACHE_KEY + wizard_hash
    cache.set(key, SETUP_WIZARD_CACHE_EMPTY, SETUP_WIZARD_CACHE_TIMEOUT)
    return {"hash": wizard_hash}


@router.get("wizard/{wizard_hash}/")
async def setup_wizard_hash(request, wizard_hash: str, auth=None):
    """
    Last step used by sentry-wizard
    For a specified hash, fetch data for projects with organizations and dsn keys
    Hash replaces user authentication
    """
    key = SETUP_WIZARD_CACHE_KEY + wizard_hash
    wizard_data = cache.get(key)

    if wizard_data is None:
        raise Http404
    elif wizard_data == SETUP_WIZARD_CACHE_EMPTY:
        raise HttpError(400)

    return wizard_data


@router.delete("wizard/{wizard_hash}/")
def setup_wizard_delete(request, wizard_hash: str, auth=None):
    """
    Delete hash used by sentry-wizard.
    It contains sensitive data, so it makes sense to remove when done.
    """
    cache.delete(SETUP_WIZARD_CACHE_KEY + wizard_hash)


@router.post("wizard-set-token/")
async def setup_wizard_set_token(request: AuthHttpRequest, payload: SetupWizardSchema):
    """
    Authenticated api for storing projects data to later be used by sentry-wizard
    """
    wizard_hash = payload.hash
    key = SETUP_WIZARD_CACHE_KEY + wizard_hash
    wizard_data = cache.get(key)
    if wizard_data is None:
        raise HttpError(400, "Token not found")

    user_id = request.auth.user_id
    projects = [
        project
        async for project in Project.annotate_is_member(
            Project.objects.filter(organization__users=user_id), user_id
        )
        .select_related("organization")
        .prefetch_related("projectkey_set")[:50]
    ]

    scope = getattr(APIToken.scopes, "project:releases")
    token = await APIToken.objects.filter(user=user_id, scopes=scope).afirst()
    if not token:
        token = await APIToken.objects.acreate(user_id=user_id, scopes=scope)

    result = SetupWizardResultSchema(api_keys=token, projects=projects)
    cache.set(key, result.dict(by_alias=True), SETUP_WIZARD_CACHE_TIMEOUT)