package com.tt.honeyDue.network

import com.tt.honeyDue.models.PresignUploadRequest
import com.tt.honeyDue.models.PresignUploadResponse
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.utils.io.core.*

/**
 * Three-step direct-to-B2 upload helper.
 *
 * Step 1: [presign] — call POST /api/uploads/presign on our API. Returns a
 *         B2 POST policy plus form fields the client needs to perform the
 *         direct upload.
 * Step 2: [postToStorage] — multipart/form-data POST straight to B2.
 *         Bytes never traverse our API server.
 * Step 3: caller invokes the relevant entity-creation endpoint
 *         (POST /api/task-completions/, POST /api/documents/) with the
 *         returned upload_id in the `upload_ids` field.
 *
 * iOS uses its own native equivalent (PresignedUploader.swift) for memory
 * reasons — Swift can stream a multipart body without buffering. Android
 * uses this Kotlin path which works fine for ≤10 MB images.
 */
class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
    private val baseUrl = ApiClient.getBaseUrl()

    /** Step 1 — request a signed POST policy. */
    suspend fun presign(
        token: String,
        category: String,
        contentType: String,
        contentLength: Long,
    ): ApiResult<PresignUploadResponse> {
        return try {
            val response = client.post("$baseUrl/uploads/presign/") {
                header("Authorization", "Token $token")
                contentType(ContentType.Application.Json)
                setBody(PresignUploadRequest(category, contentType, contentLength))
            }
            if (response.status.isSuccess()) {
                ApiResult.Success(response.body())
            } else {
                ApiResult.Error(
                    when (response.status.value) {
                        413 -> "That photo is too large after resizing."
                        422 -> "That image format isn't supported."
                        429 -> "Too many uploads in flight; try again shortly."
                        else -> "Couldn't start upload (HTTP ${response.status.value})."
                    },
                    response.status.value,
                )
            }
        } catch (e: Exception) {
            ApiResult.Error(e.message ?: "Network error during presign")
        }
    }

    /**
     * Step 2 — POST `data` directly to B2 using the signed policy fields.
     *
     * The S3 POST policy spec requires every signed field to appear before
     * the file part, and `key` + `Content-Type` must match the policy
     * exactly. Ktor's MultiPartFormDataContent preserves insertion order
     * for the appended parts.
     */
    suspend fun postToStorage(
        uploadUrl: String,
        fields: Map<String, String>,
        data: ByteArray,
        contentType: String,
        fileName: String,
    ): ApiResult<Unit> {
        return try {
            val parts = formData {
                // Stable order: signed fields first, then file. We rely on
                // Ktor preserving the order in which append() is called.
                fields.forEach { (k, v) -> append(k, v) }
                append(
                    key = "file",
                    value = data,
                    headers = Headers.build {
                        append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"")
                        append(HttpHeaders.ContentType, contentType)
                    },
                )
            }
            val response = client.submitFormWithBinaryData(
                url = uploadUrl,
                formData = parts,
            )
            if (response.status.isSuccess()) {
                ApiResult.Success(Unit)
            } else {
                val body = try {
                    response.bodyAsText()
                } catch (_: Throwable) {
                    ""
                }
                ApiResult.Error(
                    "Upload to storage failed (HTTP ${response.status.value}): $body",
                    response.status.value,
                )
            }
        } catch (e: Exception) {
            ApiResult.Error(e.message ?: "Network error during upload")
        }
    }

    /**
     * Step 1 + Step 2 in one call. Returns the upload_id the caller passes
     * back via upload_ids[] on the entity-creation endpoint.
     *
     * Errors at either step short-circuit and surface up — the partial
     * pending_uploads row created at presign time will be reaped by the
     * server-side hourly cleanup cron.
     */
    suspend fun uploadOne(
        token: String,
        category: String,
        contentType: String,
        data: ByteArray,
        fileName: String,
    ): ApiResult<Int> {
        val presignResult = presign(token, category, contentType, data.size.toLong())
        val presigned = (presignResult as? ApiResult.Success)?.data
            ?: return ApiResult.Error(
                (presignResult as? ApiResult.Error)?.message ?: "Presign failed",
                (presignResult as? ApiResult.Error)?.code,
            )

        val postResult = postToStorage(
            uploadUrl = presigned.uploadUrl,
            fields = presigned.fields,
            data = data,
            contentType = contentType,
            fileName = fileName,
        )
        return when (postResult) {
            is ApiResult.Success -> ApiResult.Success(presigned.id)
            is ApiResult.Error -> postResult
            else -> ApiResult.Error("Upload failed in unknown state")
        }
    }
}
