package ru.forbion.f7cloud.feature.deck

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

/**
 * Deck через внутренний фронт-API (`/index.php/apps/deck/api/v1.0`) — тот же, что у веб-версии.
 * Пишущие запросы авторизуются app-password + OCS-APIRequest (CSRF при app-password не требуется).
 */
class DeckRepository {
    private fun apiBase(session: AuthSession): String =
        "${session.serverUrl.trimEnd('/')}/index.php/apps/deck/api/v1.0"

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

    // --- Чтение ---

    suspend fun loadBoards(session: AuthSession): List<DeckBoard> {
        val json = getJson(client(session), "${apiBase(session)}/boards")
        val array = json as? JSONArray ?: (json as? JSONObject)?.let { JSONArray().put(it) } ?: JSONArray()
        val out = mutableListOf<DeckBoard>()
        for (i in 0 until array.length()) {
            val board = array.optJSONObject(i) ?: continue
            val id = board.optInt("id", 0)
            if (id > 0 && !board.optBoolean("archived", false)) {
                out += DeckBoard(
                    id = id,
                    title = board.optString("title"),
                    color = board.optString("color"),
                    canEdit = board.optJSONObject("permissions")?.optBoolean("PERMISSION_EDIT", true) ?: true,
                )
            }
        }
        return out
    }

    suspend fun loadCard(session: AuthSession, cardId: Int): DeckCardDetail {
        val json = getJson(client(session), "${apiBase(session)}/cards/$cardId") as JSONObject
        val boardId = json.optInt("boardId", json.optInt("board_id", 0))
        if (boardId <= 0) error("Карточка не найдена")
        return DeckCardDetail(cardId = cardId, boardId = boardId, title = json.optString("title"))
    }

    suspend fun loadBoardDetail(session: AuthSession, boardId: Int): DeckBoardDetail {
        val c = client(session)
        // Доска-объект (метки/права) — НЕ критична: если запрос упадёт, доску всё равно
        // открываем со стеками (раньше был баг — сбой board#read рушил всю загрузку).
        var boardTitle = ""
        var labels = emptyList<DeckLabel>()
        var canEdit = true
        runCatching { getJson(c, "${apiBase(session)}/boards/$boardId") as JSONObject }
            .onSuccess { board ->
                boardTitle = board.optString("title")
                labels = parseLabels(board.optJSONArray("labels"))
                canEdit = board.optJSONObject("permissions")?.optBoolean("PERMISSION_EDIT", true) ?: true
            }
        val stacksJson = getJson(c, "${apiBase(session)}/boards/$boardId/stacks") as? JSONArray ?: JSONArray()
        val stacks = mutableListOf<DeckStack>()
        for (i in 0 until stacksJson.length()) {
            val stack = stacksJson.optJSONObject(i) ?: continue
            val stackId = stack.optInt("id", 0)
            if (stackId <= 0) continue
            val cardsArray = stack.optJSONArray("cards") ?: JSONArray()
            val cards = mutableListOf<DeckCard>()
            for (cIdx in 0 until cardsArray.length()) {
                cardsArray.optJSONObject(cIdx)?.let { cards += parseCard(it, stackId) }
            }
            stacks += DeckStack(
                id = stackId,
                boardId = boardId,
                title = stack.optString("title").ifBlank { "Колонка" },
                order = stack.optInt("order", i),
                cards = cards.sortedBy { it.order },
            )
        }
        return DeckBoardDetail(
            boardId = boardId,
            title = boardTitle,
            canEdit = canEdit,
            labels = labels,
            stacks = stacks.sortedBy { it.order },
        )
    }

    private fun parseCard(card: JSONObject, stackId: Int): DeckCard = DeckCard(
        id = card.optInt("id", 0),
        title = card.optString("title"),
        done = !card.isNull("done") && card.optString("done").isNotBlank(),
        description = card.optString("description"),
        duedate = card.optString("duedate").takeIf { it.isNotBlank() && it != "null" },
        order = card.optInt("order", 0),
        stackId = stackId,
        labels = parseLabels(card.optJSONArray("labels")),
        assignees = parseAssignees(card.optJSONArray("assignedUsers")),
    )

    private fun parseLabels(array: JSONArray?): List<DeckLabel> {
        if (array == null) return emptyList()
        val out = mutableListOf<DeckLabel>()
        for (i in 0 until array.length()) {
            val l = array.optJSONObject(i) ?: continue
            val id = l.optInt("id", 0)
            if (id > 0) out += DeckLabel(id = id, title = l.optString("title"), color = l.optString("color"))
        }
        return out
    }

    private fun parseAssignees(array: JSONArray?): List<String> {
        if (array == null) return emptyList()
        val out = mutableListOf<String>()
        for (i in 0 until array.length()) {
            val a = array.optJSONObject(i) ?: continue
            val p = a.optJSONObject("participant")
            val name = p?.optString("displayname")?.takeIf { it.isNotBlank() }
                ?: p?.optString("uid")?.takeIf { it.isNotBlank() }
                ?: a.optString("displayname")
            if (name.isNotBlank()) out += name
        }
        return out
    }

    // --- Запись ---

    fun createCard(session: AuthSession, stackId: Int, title: String): DeckCard {
        val body = JSONObject().put("title", title).put("type", "plain").put("order", 999).put("stackId", stackId)
        val json = sendJson(session, "POST", "${apiBase(session)}/cards", body) as? JSONObject
        return json?.let { parseCard(it, stackId) }
            ?: DeckCard(id = 0, title = title, done = false, stackId = stackId)
    }

    fun updateCard(
        session: AuthSession,
        card: DeckCard,
        title: String,
        description: String,
        duedate: String?,
    ) {
        val body = JSONObject()
            .put("title", title)
            .put("type", "plain")
            .put("owner", session.username)
            .put("description", description)
            .put("order", card.order)
            .put("stackId", card.stackId)
            .put("duedate", duedate ?: JSONObject.NULL)
        sendJson(session, "PUT", "${apiBase(session)}/cards/${card.id}", body)
    }

    fun setCardDone(session: AuthSession, cardId: Int, done: Boolean) {
        val path = if (done) "done" else "undone"
        sendJson(session, "PUT", "${apiBase(session)}/cards/$cardId/$path", JSONObject())
    }

    fun moveCard(session: AuthSession, cardId: Int, targetStackId: Int, order: Int = 0) {
        val body = JSONObject().put("stackId", targetStackId).put("order", order)
        sendJson(session, "PUT", "${apiBase(session)}/cards/$cardId/reorder", body)
    }

    fun archiveCard(session: AuthSession, cardId: Int) {
        sendJson(session, "PUT", "${apiBase(session)}/cards/$cardId/archive", JSONObject())
    }

    fun deleteCard(session: AuthSession, cardId: Int) {
        sendJson(session, "DELETE", "${apiBase(session)}/cards/$cardId", null)
    }

    fun assignLabel(session: AuthSession, cardId: Int, labelId: Int) {
        sendJson(session, "POST", "${apiBase(session)}/cards/$cardId/label/$labelId", JSONObject())
    }

    fun removeLabel(session: AuthSession, cardId: Int, labelId: Int) {
        sendJson(session, "DELETE", "${apiBase(session)}/cards/$cardId/label/$labelId", null)
    }

    fun createStack(session: AuthSession, boardId: Int, title: String): DeckStack {
        val body = JSONObject().put("title", title).put("boardId", boardId).put("order", 999)
        val json = sendJson(session, "POST", "${apiBase(session)}/stacks", body) as? JSONObject
        val id = json?.optInt("id", 0) ?: 0
        return DeckStack(id = id, boardId = boardId, title = title, order = 999, cards = emptyList())
    }

    // --- HTTP ---

    private fun getJson(client: OkHttpClient, url: String): Any {
        // GET — как в прежней рабочей версии (без доп. заголовков); запись отдельно в sendJson.
        val request = Request.Builder().url(url).build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful || response.body == null) error("Deck API HTTP ${response.code}")
            val body = response.body!!.string().trim()
            return when {
                body.startsWith("[") -> JSONArray(body)
                body.startsWith("{") -> JSONObject(body)
                else -> JSONArray()
            }
        }
    }

    private val jsonMedia = "application/json".toMediaType()

    private fun sendJson(session: AuthSession, method: String, url: String, body: JSONObject?): Any? {
        val payload = (body?.toString() ?: "{}").toRequestBody(jsonMedia)
        val builder = Request.Builder()
            .url(url)
            .header("OCS-APIRequest", "true")
            .header("Accept", "application/json")
        when (method) {
            "POST" -> builder.post(payload)
            "PUT" -> builder.put(payload)
            "DELETE" -> if (body != null) builder.delete(payload) else builder.delete()
        }
        client(session).newCall(builder.build()).execute().use { response ->
            if (!response.isSuccessful) error("Deck API HTTP ${response.code}")
            val text = response.body?.string()?.trim().orEmpty()
            return when {
                text.startsWith("{") -> JSONObject(text)
                text.startsWith("[") -> JSONArray(text)
                else -> null
            }
        }
    }
}

data class DeckBoard(
    val id: Int,
    val title: String,
    val color: String,
    val canEdit: Boolean = true,
)

data class DeckLabel(
    val id: Int,
    val title: String,
    val color: String,
)

data class DeckStack(
    val id: Int,
    val boardId: Int,
    val title: String,
    val order: Int,
    val cards: List<DeckCard>,
)

data class DeckCard(
    val id: Int,
    val title: String,
    val done: Boolean,
    val description: String = "",
    val duedate: String? = null,
    val order: Int = 0,
    val stackId: Int = 0,
    val labels: List<DeckLabel> = emptyList(),
    val assignees: List<String> = emptyList(),
)

data class DeckBoardDetail(
    val boardId: Int,
    val title: String,
    val canEdit: Boolean,
    val labels: List<DeckLabel>,
    val stacks: List<DeckStack>,
)

data class DeckCardDetail(
    val cardId: Int,
    val boardId: Int,
    val title: String,
)
