package ru.forbion.f7cloud.feature.files

import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.network.NetworkFactory
import ru.forbion.f7cloud.core.network.UnauthorizedException
import ru.forbion.f7cloud.core.network.applyOcsJson
import ru.forbion.f7cloud.core.network.isOcsSuccess
import ru.forbion.f7cloud.core.network.ocsData
import ru.forbion.f7cloud.core.network.ocsMeta
import ru.forbion.f7cloud.core.network.parseJsonObject

class FilesApiRepository {
    fun fetchUserConfig(session: AuthSession): FilesUserConfig {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/files/api/v1/configs"
        val request = Request.Builder().url(url).get().build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) {
                error("Files config HTTP ${response.code}")
            }
            val data = JSONObject(response.body!!.string()).optJSONObject("data") ?: JSONObject()
            return FilesUserConfig(
                sortFavoritesFirst = data.optBoolean("sort_favorites_first", true),
                sortFoldersFirst = data.optBoolean("sort_folders_first", true),
                folderTree = data.optBoolean("folder_tree", true),
                defaultView = data.optString("default_view", "files"),
                showHidden = data.optBoolean("show_hidden", false),
                showMimeColumn = data.optBoolean("show_mime_column", false),
                showExtensions = data.optBoolean("show_files_extensions", true),
                cropImagePreviews = data.optBoolean("crop_image_previews", true),
            )
        }
    }

    fun saveUserConfig(session: AuthSession, key: String, value: String) {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/files/api/v1/config/$key"
        val request = Request.Builder()
            .url(url)
            .put(value.toRequestBody("text/plain".toMediaType()))
            .build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) error("Files config save HTTP ${response.code}")
        }
    }

    fun fetchStorageStats(session: AuthSession): FilesStorageStats {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/files/api/v1/stats"
        val request = Request.Builder().url(url).get().build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) {
                return FilesStorageStats()
            }
            val data = JSONObject(response.body!!.string()).optJSONObject("data") ?: JSONObject()
            return FilesStorageStats(
                usedBytes = data.optLong("used", 0L),
                totalBytes = data.optLong("total", 0L),
                usedLabel = data.optString("usage", ""),
            )
        }
    }

    fun fetchFolderTree(session: AuthSession, path: String = "/", depth: Int = 2): List<FilesFolderTreeNode> {
        val client = authedClient(session)
        val encodedPath = java.net.URLEncoder.encode(path, Charsets.UTF_8.name())
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/files/api/v1/folder-tree" +
            "?path=$encodedPath&depth=$depth&format=json"
        val request = Request.Builder().url(url).applyOcsJson().get().build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) {
                return emptyList()
            }
            val root = parseJsonObject(response.body!!.string(), "дерево папок")
            val data = root.optJSONObject("ocs")?.opt("data")
            val nodes = when (data) {
                is JSONArray -> data
                else -> JSONArray()
            }
            return (0 until nodes.length()).mapNotNull { index ->
                parseTreeNode(nodes.optJSONObject(index), "")
            }
        }
    }

    fun fetchRecentFiles(session: AuthSession): List<FileItem> {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/files/api/v1/recent/"
        val request = Request.Builder().url(url).get().build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) {
                error("Recent files HTTP ${response.code}")
            }
            val files = JSONObject(response.body!!.string()).optJSONArray("files") ?: JSONArray()
            return (0 until files.length()).mapNotNull { index ->
                val file = files.optJSONObject(index) ?: return@mapNotNull null
                val name = file.optString("name").ifBlank { file.optString("basename") }
                val path = file.optString("path", "/").trim('/')
                val relative = if (path.isBlank()) name else "$path/$name".trim('/')
                FileItem(
                    name = name,
                    isDirectory = file.optInt("type") == 2 || file.optString("type") == "dir",
                    relativePath = relative,
                    fileId = file.optLong("id").takeIf { it > 0L },
                    lastModified = file.optLong("mtime").takeIf { it > 0L }?.times(1000L),
                    size = file.optLong("size").takeIf { it >= 0L },
                    mimeType = file.optString("mimetype").ifBlank { null },
                    favorite = file.optBoolean("favorite"),
                )
            }
        }
    }

    fun searchFiles(session: AuthSession, query: String): List<FilesSearchHit> {
        if (query.isBlank()) return emptyList()
        val client = authedClient(session)
        val encoded = java.net.URLEncoder.encode(query, Charsets.UTF_8.name())
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/search/providers/files/search" +
            "?term=$encoded&format=json"
        val request = Request.Builder().url(url).applyOcsJson().get().build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) {
                return emptyList()
            }
            val data = parseJsonObject(response.body!!.string(), "поиск файлов").ocsData() ?: JSONObject()
            val entries = data.optJSONArray("entries") ?: JSONArray()
            return (0 until entries.length()).mapNotNull { index ->
                val entry = entries.optJSONObject(index) ?: return@mapNotNull null
                val title = entry.optString("title").ifBlank { entry.optString("name") }
                val path = entry.optJSONObject("attributes")
                    ?.optString("path")
                    ?.trim('/')
                    .orEmpty()
                val relative = when {
                    path.isBlank() -> title
                    path.endsWith(title) -> path
                    else -> "$path/$title".trim('/')
                }
                FilesSearchHit(
                    name = title,
                    path = relative,
                    isDirectory = entry.optString("type") == "folder",
                    fileId = entry.optLong("fileId").takeIf { it > 0L },
                )
            }
        }
    }

    private fun parseTreeNode(json: JSONObject?, parentPath: String): FilesFolderTreeNode? {
        if (json == null) return null
        val basename = json.optString("basename").ifBlank { json.optString("displayName") }
        if (basename.isBlank()) return null
        val path = buildRelativePath(parentPath.trim('/'), basename)
        val childrenJson = json.optJSONArray("children") ?: JSONArray()
        val children = (0 until childrenJson.length()).mapNotNull { index ->
            parseTreeNode(childrenJson.optJSONObject(index), path)
        }
        return FilesFolderTreeNode(
            id = json.optLong("id"),
            basename = basename,
            displayName = json.optString("displayName", basename),
            path = path,
            children = children,
        )
    }

    fun createPublicShareLink(session: AuthSession, path: String, password: String = ""): String {
        val client = authedClient(session)
        val normalizedPath = if (path.startsWith("/")) path else "/$path"
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/files_sharing/api/v1/shares"
        val body = FormBody.Builder()
            .add("shareType", "3")
            .add("path", normalizedPath)
            .add("shareWith", password)
            .build()
        val request = Request.Builder().url(url).applyOcsJson().post(body).build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) {
                error("Не удалось создать ссылку (HTTP ${response.code})")
            }
            val root = parseJsonObject(response.body!!.string(), "создание ссылки")
            val meta = root.ocsMeta()
            if (!isOcsSuccess(meta)) {
                error(meta?.optString("message") ?: "Не удалось создать ссылку")
            }
            val shareUrl = root.ocsData()?.optString("url").orEmpty()
            if (shareUrl.isBlank()) error("Не удалось создать ссылку")
            return shareUrl
        }
    }

    fun fetchShares(session: AuthSession, relativePath: String): List<FileShareEntry> {
        val client = authedClient(session)
        val path = "/" + relativePath.trim('/')
        val encodedPath = java.net.URLEncoder.encode(path, Charsets.UTF_8.name())
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/files_sharing/api/v1/shares" +
            "?path=$encodedPath&reshares=true&format=json"
        val request = Request.Builder().url(url).applyOcsJson().get().build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) return emptyList()
            val root = parseJsonObject(response.body!!.string(), "общий доступ")
            val data = root.optJSONObject("ocs")?.opt("data")
            val array = when (data) {
                is JSONArray -> data
                else -> JSONArray()
            }
            return (0 until array.length()).mapNotNull { index ->
                val share = array.optJSONObject(index) ?: return@mapNotNull null
                val shareType = share.optInt("share_type", -1)
                val label = when (shareType) {
                    3 -> share.optString("note").ifBlank { "Ссылка" }
                    0 -> share.optString("share_with_displayname")
                        .ifBlank { share.optString("share_with") }
                    else -> share.optString("share_with_displayname")
                        .ifBlank { share.optString("uid_owner") }
                        .ifBlank { share.optString("share_with") }
                }.ifBlank { "Общий доступ" }
                FileShareEntry(
                    id = share.optLong("id"),
                    shareType = shareType,
                    label = label,
                    permissionsLabel = formatSharePermissions(share.optInt("permissions", 0)),
                    shareWith = share.optString("share_with"),
                )
            }
        }
    }

    fun fetchFileActivity(session: AuthSession, fileId: Long): List<FileActivityEntry> {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/activity/api/v2/activity" +
            "?format=json&object_type=files&object_id=$fileId&limit=50&sort=desc"
        val request = Request.Builder().url(url).applyOcsJson().get().build()
        client.newCall(request).execute().use { response ->
            if (response.code == 204 || !response.isSuccessful || response.body == null) return emptyList()
            val root = parseJsonObject(response.body!!.string(), "события")
            val data = root.optJSONObject("ocs")?.opt("data")
            val array = when (data) {
                is JSONArray -> data
                else -> JSONArray()
            }
            return (0 until array.length()).mapNotNull { index ->
                val activity = array.optJSONObject(index) ?: return@mapNotNull null
                val message = activity.optString("message").ifBlank { activity.optString("subject") }
                if (message.isBlank()) return@mapNotNull null
                FileActivityEntry(
                    id = activity.optLong("activity_id"),
                    author = activity.optString("user").ifBlank { "Пользователь" },
                    message = message,
                    timestamp = parseActivityTimestamp(activity),
                )
            }
        }
    }

    private fun parseActivityTimestamp(activity: JSONObject): Long {
        val datetime = activity.optString("datetime")
        if (datetime.isNotBlank()) {
            runCatching {
                java.time.Instant.parse(datetime).toEpochMilli()
            }.getOrNull()?.let { return it }
        }
        return activity.optLong("timestamp", 0L).takeIf { it > 0L }?.times(1000L) ?: 0L
    }

    private fun formatSharePermissions(permissions: Int): String {
        val read = permissions and 1 != 0
        val update = permissions and 2 != 0
        val create = permissions and 4 != 0
        val delete = permissions and 8 != 0
        val share = permissions and 16 != 0
        return when {
            update || create || delete -> "Для редактирования"
            share && read -> "Для просмотра и обмена"
            read -> "Для просмотра"
            else -> "Ограниченный доступ"
        }
    }

    private fun buildRelativePath(parent: String, name: String): String {
        val base = parent.trim('/')
        return if (base.isEmpty()) name else "$base/$name"
    }

    private fun authedClient(session: AuthSession): OkHttpClient =
        NetworkFactory.newAuthedClient(session.username, session.appPassword, session.trustAllCerts)
}
