package ru.forbion.f7cloud.core.network

import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.net.URLDecoder

object DavClient {
    data class DavEntry(
        val name: String,
        val href: String,
        val isDirectory: Boolean,
        val fileId: Long? = null,
        val lastModified: Long? = null,
        val size: Long? = null,
        val mimeType: String? = null,
        val favorite: Boolean = false,
    )

    fun mkcol(client: OkHttpClient, folderUrl: String) {
        val request = Request.Builder()
            .url(folderUrl)
            .method("MKCOL", null)
            .build()
        client.newCall(request).execute().use { response ->
            if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
                error("DAV MKCOL HTTP ${response.code}")
            }
        }
    }

    fun put(
        client: OkHttpClient,
        fileUrl: String,
        body: RequestBody,
    ) {
        val request = Request.Builder()
            .url(fileUrl)
            .put(body)
            .build()
        client.newCall(request).execute().use { response ->
            if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
                error("DAV upload HTTP ${response.code}")
            }
        }
    }

    fun propfind(client: OkHttpClient, folderUrl: String, depth: Int = 1): List<DavEntry> {
        val body = """
            <?xml version="1.0" encoding="utf-8"?>
            <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
              <d:prop>
                <d:displayname/>
                <d:resourcetype/>
                <d:getlastmodified/>
                <d:getcontentlength/>
                <d:getcontenttype/>
                <oc:fileid/>
                <oc:favorite/>
              </d:prop>
            </d:propfind>
        """.trimIndent()
        val request = Request.Builder()
            .url(folderUrl)
            .header("Depth", depth.toString())
            .method("PROPFIND", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
            .build()
        return client.newCall(request).execute().use { response ->
            val code = response.code
            if (code !in 200..299 && code != 207) {
                error("DAV error HTTP $code")
            }
            val xml = response.body?.string().orEmpty()
            if (xml.isBlank()) {
                error("DAV empty response")
            }
            parseMultiStatus(xml, folderUrl)
        }
    }

    private fun parseMultiStatus(xml: String, folderUrl: String): List<DavEntry> {
        val parser = XmlPullParserFactory.newInstance().newPullParser()
        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
        parser.setInput(xml.reader())

        val folderPath = folderUrl.toDavPath()
        val result = mutableListOf<DavEntry>()

        var inResponse = false
        var href = ""
        var displayName = ""
        var isCollection = false
        var fileId: Long? = null
        var lastModified: Long? = null
        var size: Long? = null
        var mimeType: String? = null
        var favorite = false

        while (parser.eventType != XmlPullParser.END_DOCUMENT) {
            when (parser.eventType) {
                XmlPullParser.START_TAG -> {
                    when (parser.localTag()) {
                        "response" -> {
                            inResponse = true
                            href = ""
                            displayName = ""
                            isCollection = false
                            fileId = null
                            lastModified = null
                            size = null
                            mimeType = null
                            favorite = false
                        }
                        "href" -> if (inResponse) href = parser.readText()
                        "displayname" -> if (inResponse) displayName = parser.readText()
                        "collection" -> if (inResponse) isCollection = true
                        "getlastmodified" -> if (inResponse) {
                            lastModified = parseHttpDate(parser.readText())
                        }
                        "getcontentlength" -> if (inResponse) {
                            parser.readText().toLongOrNull()?.let { size = it }
                        }
                        "getcontenttype" -> if (inResponse) {
                            mimeType = parser.readText().trim().ifBlank { null }
                        }
                        "fileid" -> if (inResponse) {
                            parser.readText().toLongOrNull()?.let { fileId = it }
                        }
                        "favorite" -> if (inResponse) {
                            favorite = parser.readText().trim() == "1"
                        }
                    }
                }
                XmlPullParser.END_TAG -> {
                    if (parser.localTag() == "response" && inResponse) {
                        inResponse = false
                        val entryPath = href.decodeHrefPath()
                        if (entryPath.isNotBlank() && entryPath != folderPath && entryPath.startsWith(folderPath)) {
                            val relative = entryPath.removePrefix(folderPath).trim('/')
                            if (relative.isNotBlank() && !relative.contains('/')) {
                                val name = displayName.trim().ifBlank {
                                    relative.substringAfterLast('/')
                                }
                                if (name.isNotBlank() && name != "." && name != "..") {
                                    result += DavEntry(
                                        name = name,
                                        href = href,
                                        isDirectory = isCollection,
                                        fileId = fileId,
                                        lastModified = lastModified,
                                        size = size,
                                        mimeType = mimeType,
                                        favorite = favorite,
                                    )
                                }
                            }
                        }
                    }
                }
            }
            parser.next()
        }
        return result.sortedWith(compareByDescending<DavEntry> { it.isDirectory }.thenBy { it.name.lowercase() })
    }

    fun delete(client: OkHttpClient, resourceUrl: String) {
        val request = Request.Builder()
            .url(resourceUrl)
            .delete()
            .build()
        client.newCall(request).execute().use { response ->
            if (response.code !in 200..299 && response.code != 204) {
                error("DAV DELETE HTTP ${response.code}")
            }
        }
    }

    fun move(client: OkHttpClient, sourceUrl: String, destinationUrl: String) {
        val request = Request.Builder()
            .url(sourceUrl)
            .method("MOVE", null)
            .header("Destination", destinationUrl)
            .header("Overwrite", "T")
            .build()
        client.newCall(request).execute().use { response ->
            if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
                error("DAV MOVE HTTP ${response.code}")
            }
        }
    }

    fun setFavorite(client: OkHttpClient, resourceUrl: String, favorite: Boolean) {
        val value = if (favorite) "1" else "0"
        val body = """
            <?xml version="1.0" encoding="utf-8"?>
            <d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
              <d:set>
                <d:prop>
                  <oc:favorite>$value</oc:favorite>
                </d:prop>
              </d:set>
            </d:propertyupdate>
        """.trimIndent()
        val request = Request.Builder()
            .url(resourceUrl)
            .method("PROPPATCH", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
            .build()
        client.newCall(request).execute().use { response ->
            if (response.code !in 200..299 && response.code != 207) {
                error("DAV PROPPATCH HTTP ${response.code}")
            }
        }
    }

    private fun parseHttpDate(raw: String): Long? {
        val trimmed = raw.trim()
        if (trimmed.isBlank()) return null
        return try {
            val format = java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", java.util.Locale.US)
            format.parse(trimmed)?.time
        } catch (_: Exception) {
            null
        }
    }

    private fun XmlPullParser.localTag(): String = name.substringAfter(':')

    private fun XmlPullParser.readText(): String {
        if (next() != XmlPullParser.TEXT) return ""
        return text.orEmpty()
    }

    private fun String.decodeHrefPath(): String {
        val path = try {
            URLDecoder.decode(this, Charsets.UTF_8.name())
        } catch (_: Exception) {
            this
        }
        return path.substringBefore('?').trim('/')
    }

    private fun String.toDavPath(): String {
        val path = if (contains("://")) {
            java.net.URI(this).path.orEmpty()
        } else {
            this
        }
        return path.decodeHrefPath()
    }
}

fun davFilesBaseUrl(serverUrl: String, userId: String): String {
    val base = serverUrl.trimEnd('/')
    val encodedUser = userId.split('/').joinToString("/") { segment ->
        java.net.URLEncoder.encode(segment, Charsets.UTF_8.name()).replace("+", "%20")
    }
    return "$base/remote.php/dav/files/$encodedUser/"
}

fun davFolderUrl(serverUrl: String, userId: String, relativePath: String): String {
    val base = davFilesBaseUrl(serverUrl, userId)
    if (relativePath.isBlank()) return base
    val encodedPath = relativePath.trim('/').split('/').joinToString("/") { segment ->
        java.net.URLEncoder.encode(segment, Charsets.UTF_8.name()).replace("+", "%20")
    }
    return "$base$encodedPath/"
}

fun davFileUrl(serverUrl: String, userId: String, relativePath: String): String {
    val base = davFilesBaseUrl(serverUrl, userId)
    if (relativePath.isBlank()) error("Путь к файлу пуст")
    val encodedPath = relativePath.trim('/').split('/').joinToString("/") { segment ->
        java.net.URLEncoder.encode(segment, Charsets.UTF_8.name()).replace("+", "%20")
    }
    return "$base$encodedPath"
}

fun davAddressBooksBaseUrl(serverUrl: String, userId: String): String {
    val base = serverUrl.trimEnd('/')
    val encodedUser = userId.split('/').joinToString("/") { segment ->
        java.net.URLEncoder.encode(segment, Charsets.UTF_8.name()).replace("+", "%20")
    }
    return "$base/remote.php/dav/addressbooks/users/$encodedUser/"
}
