"""Port of sentry.api.endpoints.chunk.ChunkUploadEndpoint"""

import logging
from gzip import GzipFile
from io import BytesIO

from django.conf import settings
from django.shortcuts import aget_object_or_404
from django.urls import reverse
from ninja import File, Router
from ninja.errors import HttpError
from ninja.files import UploadedFile

from apps.organizations_ext.models import Organization
from glitchtip.api.authentication import AuthHttpRequest
from glitchtip.api.decorators import optional_slash
from glitchtip.api.permissions import has_permission

from .models import FileBlob

# Force just one blob
CHUNK_UPLOAD_BLOB_SIZE = 32 * 1024 * 1024  # 32MB
MAX_CHUNKS_PER_REQUEST = 1
MAX_REQUEST_SIZE = CHUNK_UPLOAD_BLOB_SIZE
MAX_CONCURRENCY = 1
HASH_ALGORITHM = "sha1"

CHUNK_UPLOAD_ACCEPT = (
    "debug_files",  # DIF assemble
    "release_files",  # Release files assemble
    "pdbs",  # PDB upload and debug id override
    "sources",  # Source artifact bundle upload
    "artifact_bundles",  # Artifact bundles contain debug ids to link source to sourcemaps
    "proguard",
)


class GzipChunk(BytesIO):
    def __init__(self, file):
        raw = file.read()
        try:
            data = GzipFile(fileobj=BytesIO(raw), mode="rb").read()
        except Exception:
            # sentry-cli 3.x sends raw (uncompressed) zip data despite gzip being
            # advertised by the server — fall back to using the raw bytes as-is.
            data = raw
        self.size = len(data)
        self.name = file.name
        super().__init__(data)


router = Router()


@optional_slash(router, "get", "organizations/{slug:organization_slug}/chunk-upload/")
async def get_chunk_upload_info(request: AuthHttpRequest, organization_slug: str):
    """Get server settings for chunk file upload"""
    path = reverse("api:get_chunk_upload_info", args=[organization_slug])
    url = (
        path
        if settings.GLITCHTIP_CHUNK_UPLOAD_USE_RELATIVE_URL
        else settings.GLITCHTIP_URL.geturl() + path
    )
    return {
        "url": url,
        "chunkSize": CHUNK_UPLOAD_BLOB_SIZE,
        "chunksPerRequest": MAX_CHUNKS_PER_REQUEST,
        "maxFileSize": 2147483648,
        "maxRequestSize": MAX_REQUEST_SIZE,
        "concurrency": MAX_CONCURRENCY,
        "hashAlgorithm": HASH_ALGORITHM,
        "compression": ["gzip"],
        "accept": CHUNK_UPLOAD_ACCEPT,
    }


@optional_slash(router, "post", "organizations/{slug:organization_slug}/chunk-upload/")
@has_permission(["project:write", "project:admin", "project:releases"])
async def chunk_upload(
    request: AuthHttpRequest,
    organization_slug: str,
    file_gzip: list[UploadedFile] = File(...),
):
    """Upload one more more gzipped files to save"""
    logger = logging.getLogger("glitchtip.files")
    logger.info("chunkupload.start")

    organization = await aget_object_or_404(
        Organization, slug=organization_slug.lower(), users=request.auth.user_id
    )

    files = [GzipChunk(chunk) for chunk in file_gzip]

    if len(files) == 0:
        # No files uploaded is ok
        logger.info("chunkupload.end", extra={"status": 200})
        return

    logger.info("chunkupload.post.files", extra={"len": len(files)})

    # Validate file size
    checksums = []
    size = 0
    for chunk in files:
        size += chunk.size
        if chunk.size > CHUNK_UPLOAD_BLOB_SIZE:
            logger.info("chunkupload.end", extra={"status": 400})
            raise HttpError(400, "Chunk size too large")
        checksums.append(chunk.name)

    if size > MAX_REQUEST_SIZE:
        logger.info("chunkupload.end", extra={"status": 400})
        raise HttpError(400, "Request too large")

    if len(files) > MAX_CHUNKS_PER_REQUEST:
        logger.info("chunkupload.end", extra={"status": 400})
        raise HttpError(400, "Too many chunks")

    try:
        await FileBlob.from_files(
            zip(files, checksums), organization=organization, logger=logger
        )
    except IOError as err:
        logger.info("chunkupload.end", extra={"status": 400})
        raise HttpError(400, str(err)) from err

    logger.info("chunkupload.end", extra={"status": 200})
