package ru.forbion.f7cloud.feature.talk

import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.auth.OcsUserResolver
import ru.forbion.f7cloud.core.network.NetworkFactory
import ru.forbion.f7cloud.core.network.UnauthorizedException
import ru.forbion.f7cloud.core.network.davFileUrl
import ru.forbion.f7cloud.core.network.davFolderUrl
import java.util.concurrent.TimeUnit

object TalkAttachmentUploader {
    private const val MAX_UPLOAD_BYTES = 25L * 1024 * 1024
    private const val SHARE_TYPE_ROOM = "10"
    private const val DEFAULT_ATTACHMENT_FOLDER = "/Talk"

    fun uploadAndShare(
        session: AuthSession,
        roomToken: String,
        fileName: String,
        mimeType: String,
        bytes: ByteArray,
        caption: String = "",
        replyTo: Long? = null,
    ) {
        require(fileName.isNotBlank()) { "Имя файла пустое" }
        if (bytes.isEmpty()) error("Файл пуст")
        if (bytes.size > MAX_UPLOAD_BYTES) {
            error("Файл слишком большой (макс. 25 МБ)")
        }

        val client = uploadClient(session)
        val davUserId = OcsUserResolver.resolveDavUserId(session)
        val attachmentFolder = fetchAttachmentFolder(client, session)
        val remotePath = uniqueRemotePath(client, session, davUserId, attachmentFolder, fileName)
        ensureFolderExists(client, session, davUserId, attachmentFolder)
        putFile(client, session, davUserId, remotePath, mimeType, bytes)
        shareToRoom(client, session, roomToken, remotePath, caption, replyTo)
    }

    private fun uploadClient(session: AuthSession): OkHttpClient =
        NetworkFactory.newAuthedClient(session.username, session.appPassword, session.trustAllCerts)
            .newBuilder()
            .readTimeout(5, TimeUnit.MINUTES)
            .writeTimeout(5, TimeUnit.MINUTES)
            .callTimeout(6, TimeUnit.MINUTES)
            .build()

    private fun fetchAttachmentFolder(client: OkHttpClient, session: AuthSession): String {
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/cloud/capabilities?format=json"
        val request = Request.Builder()
            .url(url)
            .header("OCS-APIRequest", "true")
            .header("Accept", "application/json")
            .build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) return DEFAULT_ATTACHMENT_FOLDER
            val data = JSONObject(response.body!!.string())
                .optJSONObject("ocs")
                ?.optJSONObject("data")
                ?.optJSONObject("capabilities")
                ?.optJSONObject("spreed")
                ?.optJSONObject("config")
                ?.optJSONObject("attachments")
            val folder = data?.optString("folder").orEmpty().trim()
            return folder.ifBlank { DEFAULT_ATTACHMENT_FOLDER }
        }
    }

    private fun uniqueRemotePath(
        client: OkHttpClient,
        session: AuthSession,
        davUserId: String,
        attachmentFolder: String,
        fileName: String,
    ): String {
        val safeName = fileName.substringAfterLast('/').trim().ifBlank { "file" }
        var candidate = joinRemotePath(attachmentFolder, safeName)
        var counter = 2
        while (fileExists(client, session, davUserId, candidate)) {
            val dot = safeName.lastIndexOf('.')
            val renamed = if (dot > 0) {
                "${safeName.substring(0, dot)} ($counter)${safeName.substring(dot)}"
            } else {
                "$safeName ($counter)"
            }
            candidate = joinRemotePath(attachmentFolder, renamed)
            counter++
        }
        return candidate
    }

    private fun joinRemotePath(folder: String, fileName: String): String {
        val base = folder.trim('/').ifBlank { "Talk" }
        return "/$base/$fileName"
    }

    private fun fileExists(
        client: OkHttpClient,
        session: AuthSession,
        davUserId: String,
        remotePath: String,
    ): Boolean {
        val url = davFileUrl(session.serverUrl, davUserId, remotePath.trim('/'))
        val request = Request.Builder().url(url).head().build()
        client.newCall(request).execute().use { response ->
            return response.isSuccessful
        }
    }

    private fun ensureFolderExists(
        client: OkHttpClient,
        session: AuthSession,
        davUserId: String,
        attachmentFolder: String,
    ) {
        val segments = attachmentFolder.trim('/').split('/').filter { it.isNotBlank() }
        var built = ""
        for (segment in segments) {
            built = if (built.isEmpty()) segment else "$built/$segment"
            mkcol(client, session, davUserId, built)
        }
    }

    private fun mkcol(client: OkHttpClient, session: AuthSession, davUserId: String, relativePath: String) {
        val url = davFolderUrl(session.serverUrl, davUserId, relativePath).trimEnd('/')
        val request = Request.Builder()
            .url(url)
            .method("MKCOL", ByteArray(0).toRequestBody(null))
            .build()
        client.newCall(request).execute().use { response ->
            // 405 = already exists
        }
    }

    private fun putFile(
        client: OkHttpClient,
        session: AuthSession,
        davUserId: String,
        remotePath: String,
        mimeType: String,
        bytes: ByteArray,
    ) {
        val mediaType = mimeType.ifBlank { "application/octet-stream" }.toMediaType()
        val url = davFileUrl(session.serverUrl, davUserId, remotePath.trim('/'))
        val request = Request.Builder()
            .url(url)
            .put(bytes.toRequestBody(mediaType))
            .build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                error("Не удалось загрузить файл: HTTP ${response.code}")
            }
        }
    }

    private fun shareToRoom(
        client: OkHttpClient,
        session: AuthSession,
        roomToken: String,
        remotePath: String,
        caption: String,
        replyTo: Long?,
    ) {
        val meta = JSONObject()
        if (caption.isNotBlank()) meta.put("caption", caption)
        if (replyTo != null && replyTo > 0L) meta.put("replyTo", replyTo.toString())

        val body = FormBody.Builder()
            .add("path", remotePath)
            .add("shareWith", roomToken)
            .add("shareType", SHARE_TYPE_ROOM)
            .add("talkMetaData", meta.toString())
            .build()

        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/files_sharing/api/v1/shares"
        val request = Request.Builder()
            .url(url)
            .header("OCS-APIRequest", "true")
            .header("Accept", "application/json")
            .post(body)
            .build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                error("Не удалось отправить вложение в чат: HTTP ${response.code}")
            }
            val ocs = JSONObject(response.body?.string().orEmpty()).optJSONObject("ocs")
            if (ocs?.optJSONObject("meta")?.optString("status") == "failure") {
                error(ocs.optJSONObject("meta")?.optString("message") ?: "Share failed")
            }
        }
    }
}
