package com.syncflow.data.providers.google

import com.syncflow.data.providers.CloudProvider
import com.syncflow.domain.model.CloudAccount
import com.syncflow.domain.model.RemoteFile
import kotlinx.serialization.json.*
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.InputStream
import java.io.OutputStream
import java.time.Instant

class GoogleDriveProvider(private val account: CloudAccount) : CloudProvider {

    private val token: String by lazy {
        Json.parseToJsonElement(account.credentialJson).jsonObject["access_token"]?.jsonPrimitive?.content ?: ""
    }

    private val client = OkHttpClient()

    private fun auth(builder: Request.Builder) = builder.header("Authorization", "Bearer $token")

    override suspend fun testConnection(): Result<Unit> = runCatching {
        val req = auth(Request.Builder().url("https://www.googleapis.com/drive/v3/about?fields=user")).build()
        client.newCall(req).execute().use { resp -> if (!resp.isSuccessful) throw Exception("HTTP ${resp.code}") }
    }

    override suspend fun listFiles(remotePath: String): Result<List<RemoteFile>> = runCatching {
        val folderId = if (remotePath == "/" || remotePath.isBlank()) "root" else remotePath
        val q = "'$folderId' in parents and trashed = false"
        val url = "https://www.googleapis.com/drive/v3/files?q=${q.encodeUrl()}&fields=files(id,name,mimeType,size,modifiedTime,md5Checksum)&pageSize=1000"
        val req = auth(Request.Builder().url(url)).build()
        client.newCall(req).execute().use { resp ->
            val body = resp.body?.string() ?: throw Exception("HTTP ${resp.code}")
            if (!resp.isSuccessful) throw Exception("HTTP ${resp.code}: $body")
            val files = Json.parseToJsonElement(body).jsonObject["files"]?.jsonArray ?: return@use emptyList()
            files.map { it.jsonObject.toDriveFile() }
        }
    }

    override suspend fun uploadFile(localStream: InputStream, remotePath: String, sizeBytes: Long, onProgress: (Long) -> Unit): Result<RemoteFile> = runCatching {
        val bytes = localStream.readBytes()
        val name = remotePath.substringAfterLast('/')
        val parentId = if ('/' in remotePath.dropLast(1)) remotePath.substringBeforeLast('/') else "root"

        // Multipart upload — use JSON builder to avoid injection via filenames with special chars
        val metaPart = buildJsonObject {
            put("name", name)
            put("parents", buildJsonArray { add(parentId) })
        }.toString().toRequestBody("application/json".toMediaType())
        val dataPart = bytes.toRequestBody("application/octet-stream".toMediaType())
        val multipart = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addPart(metaPart)
            .addPart(dataPart)
            .build()

        val url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,mimeType,size,modifiedTime,md5Checksum"
        val req = auth(Request.Builder().url(url).post(multipart)).build()
        client.newCall(req).execute().use { resp ->
            val body = resp.body?.string() ?: throw Exception("HTTP ${resp.code}")
            if (!resp.isSuccessful) throw Exception("HTTP ${resp.code}: $body")
            onProgress(bytes.size.toLong())
            Json.parseToJsonElement(body).jsonObject.toDriveFile()
        }
    }

    override suspend fun downloadFile(remotePath: String, destination: OutputStream, onProgress: (Long) -> Unit): Result<Unit> = runCatching {
        val fileId = remotePath
        val req = auth(Request.Builder().url("https://www.googleapis.com/drive/v3/files/$fileId?alt=media")).build()
        client.newCall(req).execute().use { resp ->
            if (!resp.isSuccessful) throw Exception("HTTP ${resp.code}")
            var total = 0L
            resp.body?.byteStream()?.use { src ->
                val buf = ByteArray(65536)
                var n: Int
                while (src.read(buf).also { n = it } != -1) { destination.write(buf, 0, n); total += n; onProgress(total) }
            }
        }
    }

    override suspend fun deleteFile(remotePath: String): Result<Unit> = runCatching {
        val req = auth(Request.Builder().url("https://www.googleapis.com/drive/v3/files/$remotePath").delete()).build()
        client.newCall(req).execute().use { resp -> if (!resp.isSuccessful && resp.code != 404) throw Exception("HTTP ${resp.code}") }
    }

    override suspend fun createDirectory(remotePath: String): Result<Unit> = runCatching {
        val name = remotePath.substringAfterLast('/')
        val parentId = if ('/' in remotePath.dropLast(1)) remotePath.substringBeforeLast('/') else "root"
        val body = buildJsonObject {
            put("name", name)
            put("mimeType", "application/vnd.google-apps.folder")
            put("parents", buildJsonArray { add(parentId) })
        }.toString().toRequestBody("application/json".toMediaType())
        val req = auth(Request.Builder().url("https://www.googleapis.com/drive/v3/files").post(body)).build()
        client.newCall(req).execute().use { resp -> if (!resp.isSuccessful) throw Exception("HTTP ${resp.code}") }
    }

    override suspend fun getFileMetadata(remotePath: String): Result<RemoteFile> = runCatching {
        val req = auth(Request.Builder().url("https://www.googleapis.com/drive/v3/files/$remotePath?fields=id,name,mimeType,size,modifiedTime,md5Checksum")).build()
        client.newCall(req).execute().use { resp ->
            val body = resp.body?.string() ?: throw Exception("HTTP ${resp.code}")
            Json.parseToJsonElement(body).jsonObject.toDriveFile()
        }
    }

    override suspend fun moveFile(fromPath: String, toPath: String): Result<Unit> = runCatching {
        val newName = toPath.substringAfterLast('/')
        val body = buildJsonObject { put("name", newName) }.toString()
            .toRequestBody("application/json".toMediaType())
        val req = auth(Request.Builder().url("https://www.googleapis.com/drive/v3/files/$fromPath").patch(body)).build()
        client.newCall(req).execute().use { resp -> if (!resp.isSuccessful) throw Exception("HTTP ${resp.code}") }
    }

    private fun JsonObject.toDriveFile() = RemoteFile(
        path = get("id")?.jsonPrimitive?.content ?: "",
        name = get("name")?.jsonPrimitive?.content ?: "",
        isDirectory = get("mimeType")?.jsonPrimitive?.content == "application/vnd.google-apps.folder",
        sizeBytes = get("size")?.jsonPrimitive?.content?.toLongOrNull() ?: 0L,
        modifiedAt = get("modifiedTime")?.jsonPrimitive?.content?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: Instant.EPOCH,
        etag = get("md5Checksum")?.jsonPrimitive?.content,
        mimeType = get("mimeType")?.jsonPrimitive?.content,
    )

    private fun String.encodeUrl() = java.net.URLEncoder.encode(this, "UTF-8")
    private fun Request.Builder.patch(body: RequestBody) = method("PATCH", body)
}
