package ru.forbion.f7cloud.feature.calendar

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.ocsMeta
import ru.forbion.f7cloud.core.network.parseJsonObject

data class CalendarAttendeeSuggestion(
    val name: String,
    val email: String,
    val type: String,
)

data class CalendarLocationSuggestion(
    val name: String,
    val address: String,
)

data class CalendarTalkRoom(
    val token: String,
    val displayName: String,
    val callUrl: String,
)

data class CalendarShareeSuggestion(
    val displayName: String,
    /** DAV-принципал: principal:principals/users/<id> или groups/<id>. */
    val principal: String,
    val isGroup: Boolean,
)

data class CalendarUserSettings(
    val timezone: String = "automatic",
    val showWeekends: Boolean = true,
    val showWeekNumbers: Boolean = false,
    val defaultReminderMinutes: Int = 15,
    val showTasks: Boolean = true,
    val tasksSidebar: Boolean = true,
)

class CalendarApiClient {
    fun searchAttendees(session: AuthSession, query: String): List<CalendarAttendeeSuggestion> {
        if (query.trim().length < 2) return emptyList()
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/calendar/v1/autocompletion/attendee"
        val body = JSONObject().put("search", query.trim()).toString()
        val request = Request.Builder()
            .url(url)
            .post(body.toRequestBody("application/json; charset=utf-8".toMediaType()))
            .applyOcsJson()
            .build()
        client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "autocomplete attendee")
            val data = json.optJSONArray("data") ?: json.optJSONObject("ocs")?.optJSONArray("data") ?: return emptyList()
            return buildList {
                for (i in 0 until data.length()) {
                    val item = data.optJSONObject(i) ?: continue
                    val emails = item.optJSONArray("emails")
                    val email = emails?.optString(0).orEmpty()
                    if (email.isBlank()) continue
                    add(
                        CalendarAttendeeSuggestion(
                            name = item.optString("name", email),
                            email = email,
                            type = item.optString("type", "individual"),
                        ),
                    )
                }
            }
        }
    }

    fun searchLocations(session: AuthSession, query: String): List<CalendarLocationSuggestion> {
        if (query.trim().length < 2) return emptyList()
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/calendar/v1/autocompletion/location?search=${java.net.URLEncoder.encode(query.trim(), Charsets.UTF_8.name())}"
        val request = Request.Builder()
            .url(url)
            .get()
            .applyOcsJson()
            .build()
        client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "autocomplete location")
            val data = json.optJSONArray("data") ?: return emptyList()
            return buildList {
                for (i in 0 until data.length()) {
                    val item = data.optJSONObject(i) ?: continue
                    add(
                        CalendarLocationSuggestion(
                            name = item.optString("name", ""),
                            address = item.optString("address", item.optString("label", "")),
                        ),
                    )
                }
            }
        }
    }

    fun setConfig(session: AuthSession, key: String, value: String) {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/index.php/apps/calendar/v1/config/$key"
        val body = JSONObject().put("value", value).toString()
        val request = Request.Builder()
            .url(url)
            .post(body.toRequestBody("application/json; charset=utf-8".toMediaType()))
            .applyOcsJson()
            .build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) error("Calendar config failed HTTP ${response.code}")
        }
    }

    /** Email текущего пользователя (для ORGANIZER в iTIP free-busy). */
    fun getUserEmail(session: AuthSession): String {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/cloud/user?format=json"
        val request = Request.Builder().url(url).get().applyOcsJson().build()
        return client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "user info")
            json.optJSONObject("ocs")?.optJSONObject("data")?.optString("email").orEmpty()
        }
    }

    /** Поиск пользователей/групп для шаринга календаря (OCS sharees). */
    fun searchSharees(session: AuthSession, query: String): List<CalendarShareeSuggestion> {
        if (query.trim().length < 2) return emptyList()
        val client = authedClient(session)
        val q = java.net.URLEncoder.encode(query.trim(), Charsets.UTF_8.name())
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v1.php/apps/files_sharing/api/v1/sharees" +
            "?format=json&itemType=calendar&perPage=10&search=$q"
        val request = Request.Builder().url(url).get().applyOcsJson().build()
        return client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "sharees")
            val data = json.optJSONObject("ocs")?.optJSONObject("data") ?: return emptyList()
            val out = mutableListOf<CalendarShareeSuggestion>()
            fun collect(arrName: String, isGroup: Boolean) {
                val exactWrap = data.optJSONObject("exact")?.optJSONArray(arrName) ?: JSONArray()
                val plain = data.optJSONArray(arrName) ?: JSONArray()
                for (arr in listOf(exactWrap, plain)) {
                    for (i in 0 until arr.length()) {
                        val item = arr.optJSONObject(i) ?: continue
                        val id = item.optJSONObject("value")?.optString("shareWith").orEmpty()
                        if (id.isBlank()) continue
                        val kind = if (isGroup) "groups" else "users"
                        out += CalendarShareeSuggestion(
                            displayName = item.optString("label").ifBlank { id },
                            principal = "principal:principals/$kind/$id",
                            isGroup = isGroup,
                        )
                    }
                }
            }
            collect("users", isGroup = false)
            collect("groups", isGroup = true)
            out.distinctBy { it.principal }
        }
    }

    /** Существующие беседы Talk (для выбора комнаты в событии). */
    fun listTalkRooms(session: AuthSession): List<CalendarTalkRoom> {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/spreed/api/v4/room?format=json"
        val request = Request.Builder().url(url).get().applyOcsJson().build()
        return client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "list talk rooms")
            if (!isOcsSuccess(json.ocsMeta())) return emptyList()
            val data = json.optJSONObject("ocs")?.optJSONArray("data") ?: JSONArray()
            val out = mutableListOf<CalendarTalkRoom>()
            for (i in 0 until data.length()) {
                val room = data.optJSONObject(i) ?: continue
                val token = room.optString("token")
                if (token.isBlank()) continue
                val name = room.optString("displayName").ifBlank { room.optString("name") }
                out += CalendarTalkRoom(
                    token = token,
                    displayName = name.ifBlank { "Беседа" },
                    callUrl = "${session.serverUrl.trimEnd('/')}/call/$token",
                )
            }
            out
        }
    }

    /** Создать беседу заданного типа (2 — групповая/публичная, 3 — публичная по ссылке). */
    fun createTalkRoomTyped(session: AuthSession, roomName: String, roomType: Int): CalendarTalkRoom {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/spreed/api/v4/room?format=json"
        val payload = JSONObject()
            .put("roomType", roomType)
            .put("roomName", roomName.trim().ifBlank { "Встреча" })
            .put("readOnly", 0)
            .put("listable", 0)
            .toString()
        val request = Request.Builder()
            .url(url)
            .post(payload.toRequestBody("application/json; charset=utf-8".toMediaType()))
            .applyOcsJson()
            .build()
        client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "create talk room")
            if (!isOcsSuccess(json.ocsMeta())) error("Не удалось создать беседу Talk")
            val token = json.optJSONObject("ocs")?.optJSONObject("data")?.optString("token").orEmpty()
            if (token.isBlank()) error("Не удалось создать беседу Talk")
            return CalendarTalkRoom(
                token = token,
                displayName = roomName.trim().ifBlank { "Встреча" },
                callUrl = "${session.serverUrl.trimEnd('/')}/call/$token",
            )
        }
    }

    fun createTalkRoom(session: AuthSession, roomName: String): String {
        val client = authedClient(session)
        val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/spreed/api/v4/room?format=json"
        val payload = JSONObject()
            .put("roomType", 2)
            .put("roomName", roomName.trim().ifBlank { "Встреча" })
            .put("readOnly", 0)
            .put("listable", 0)
            .toString()
        val request = Request.Builder()
            .url(url)
            .post(payload.toRequestBody("application/json; charset=utf-8".toMediaType()))
            .applyOcsJson()
            .build()
        client.newCall(request).execute().use { response ->
            val json = parseJsonObject(response.body?.string().orEmpty(), "create talk room")
            val meta = json.ocsMeta()
            if (!isOcsSuccess(meta)) error("Не удалось создать комнату Talk")
            val token = json.optJSONObject("ocs")?.optJSONObject("data")?.optString("token").orEmpty()
            if (token.isBlank()) error("Не удалось создать комнату Talk")
            return "${session.serverUrl.trimEnd('/')}/call/$token"
        }
    }

    fun addTalkParticipants(session: AuthSession, callUrl: String, emails: List<String>) {
        val token = callUrl.substringAfter("/call/").substringBefore('?').substringBefore('#')
        if (token.isBlank()) return
        val client = authedClient(session)
        emails.forEach { email ->
            val url = "${session.serverUrl.trimEnd('/')}/ocs/v2.php/apps/spreed/api/v4/room/$token/participants?format=json"
            val payload = JSONObject()
                .put("newParticipant", email)
                .put("source", "emails")
                .toString()
            val request = Request.Builder()
                .url(url)
                .post(payload.toRequestBody("application/json; charset=utf-8".toMediaType()))
                .applyOcsJson()
                .build()
            runCatching {
                client.newCall(request).execute().use { /* best effort */ }
            }
        }
    }

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