"""R2 client for the import-staging bucket (file-upload imports).

Uses boto3 with the S3-compatible R2 endpoint. The import staging bucket is
separate from per-project runtime buckets managed by P2HS Cloud.

Single-part uploads:  ≤ 1 GB  — one presigned PUT URL.
Multipart uploads:    ≤ 20 GB — creates a multipart upload, returns per-part
                                 presigned URLs (100 MB parts).
"""
from __future__ import annotations

import uuid
from typing import Any

SINGLE_PART_MAX_BYTES = 1 * 1024 ** 3        # 1 GB
MULTIPART_MAX_BYTES   = 20 * 1024 ** 3       # 20 GB
PART_SIZE_BYTES       = 100 * 1024 * 1024    # 100 MB
PRESIGN_TTL_SECONDS   = 3600                 # 1 hour


def _r2_endpoint(account_id: str) -> str:
    return f"https://{account_id}.r2.cloudflarestorage.com"


def _client(settings):
    import boto3

    if not (
        settings.r2_imports_account_id
        and settings.r2_imports_bucket
        and settings.r2_imports_access_key_id
        and settings.r2_imports_secret_access_key
    ):
        # Without this guard, an empty account_id produces a malformed endpoint
        # (https://.r2.cloudflarestorage.com) and boto3 raises a bare ValueError
        # deep in botocore's endpoint resolution -- correct but useless for
        # triage. Fail here instead, at the one place that actually knows why.
        raise RuntimeError(
            "R2_IMPORTS_ACCOUNT_ID, R2_IMPORTS_BUCKET, R2_IMPORTS_ACCESS_KEY_ID, and "
            "R2_IMPORTS_SECRET_ACCESS_KEY must all be configured to use R2 uploads "
            "(project imports and agent-access asset uploads both depend on this)."
        )
    return boto3.client(
        "s3",
        endpoint_url=_r2_endpoint(settings.r2_imports_account_id),
        aws_access_key_id=settings.r2_imports_access_key_id,
        aws_secret_access_key=settings.r2_imports_secret_access_key,
        region_name="auto",
    )


def generate_single_part_upload_url(
    settings,
    *,
    filename: str,
    key: str | None = None,
    content_type: str | None = None,
    expires_in: int = PRESIGN_TTL_SECONDS,
) -> dict[str, Any]:
    """Return a presigned PUT URL for a single-part upload (≤ 1 GB).

    ``key`` lets callers outside the import flow (e.g. agent-access asset
    uploads) place the object under their own prefix instead of ``imports/``.
    ``content_type``, when given, is baked into the signature -- the PUT must
    send that exact ``Content-Type`` header or R2 rejects it, which is what
    lets the confirming step trust the stored object's declared MIME type.
    """
    key = key or f"imports/{uuid.uuid4().hex}/{filename}"
    s3 = _client(settings)
    params: dict[str, Any] = {"Bucket": settings.r2_imports_bucket, "Key": key}
    if content_type:
        params["ContentType"] = content_type
    url = s3.generate_presigned_url("put_object", Params=params, ExpiresIn=expires_in)
    return {"upload_type": "single", "upload_url": url, "r2_key": key}


def head_object(settings, *, r2_key: str) -> dict[str, Any] | None:
    """Return {content_length, content_type} for an existing object, or None if absent."""
    s3 = _client(settings)
    try:
        resp = s3.head_object(Bucket=settings.r2_imports_bucket, Key=r2_key)
    except Exception:
        return None
    return {
        "content_length": resp.get("ContentLength"),
        "content_type": resp.get("ContentType"),
    }


def create_multipart_upload(settings, *, filename: str) -> dict[str, Any]:
    """Initiate a multipart upload and return per-part presigned URLs (≤ 20 GB)."""
    key = f"imports/{uuid.uuid4().hex}/{filename}"
    s3 = _client(settings)
    resp = s3.create_multipart_upload(Bucket=settings.r2_imports_bucket, Key=key)
    upload_id = resp["UploadId"]
    # Pre-generate MAX_PARTS presigned URLs; the client only uses as many as needed.
    max_parts = MULTIPART_MAX_BYTES // PART_SIZE_BYTES + 1
    parts = [
        {
            "part_number": part_number,
            "upload_url": s3.generate_presigned_url(
                "upload_part",
                Params={
                    "Bucket": settings.r2_imports_bucket,
                    "Key": key,
                    "UploadId": upload_id,
                    "PartNumber": part_number,
                },
                ExpiresIn=PRESIGN_TTL_SECONDS,
            ),
        }
        for part_number in range(1, max_parts + 1)
    ]
    return {
        "upload_type": "multipart",
        "upload_id": upload_id,
        "r2_key": key,
        "parts": parts,
        "part_size_bytes": PART_SIZE_BYTES,
    }


def complete_multipart_upload(
    settings,
    *,
    r2_key: str,
    upload_id: str,
    parts: list[dict[str, Any]],
) -> None:
    """Complete a multipart upload. parts: list of {part_number, etag}."""
    s3 = _client(settings)
    s3.complete_multipart_upload(
        Bucket=settings.r2_imports_bucket,
        Key=r2_key,
        UploadId=upload_id,
        MultipartUpload={
            "Parts": [
                {"PartNumber": p["part_number"], "ETag": p["etag"]}
                for p in sorted(parts, key=lambda x: x["part_number"])
            ]
        },
    )


def abort_multipart_upload(settings, *, r2_key: str, upload_id: str) -> None:
    s3 = _client(settings)
    try:
        s3.abort_multipart_upload(
            Bucket=settings.r2_imports_bucket, Key=r2_key, UploadId=upload_id
        )
    except Exception:
        pass


def delete_object(settings, *, r2_key: str) -> None:
    s3 = _client(settings)
    try:
        s3.delete_object(Bucket=settings.r2_imports_bucket, Key=r2_key)
    except Exception:
        pass


def download_object(settings, *, r2_key: str, dest_path: str) -> None:
    s3 = _client(settings)
    s3.download_file(settings.r2_imports_bucket, r2_key, dest_path)
