package ru.forbion.f7cloud.feature.mail

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import ru.forbion.f7cloud.core.auth.AuthSession
import java.io.File
import java.security.MessageDigest

class MailCacheRepository(context: Context) {
    private val rootDir = File(context.filesDir, "mail-cache")

    suspend fun loadBootstrap(session: AuthSession): MailBootstrap? = withContext(Dispatchers.IO) {
        readJson(session, "bootstrap.json")?.let(::parseBootstrap)
    }

    suspend fun saveBootstrap(session: AuthSession, bootstrap: MailBootstrap) = withContext(Dispatchers.IO) {
        writeJson(session, "bootstrap.json", bootstrapToJson(bootstrap))
    }

    suspend fun loadFolderPage(session: AuthSession, folder: MailFolderEntry): MailMessagesPage? =
        withContext(Dispatchers.IO) {
            readJson(session, "folders/${folder.cacheKey()}.json")?.let(::parseFolderPage)
        }

    suspend fun saveFolderPage(session: AuthSession, folder: MailFolderEntry, page: MailMessagesPage) =
        withContext(Dispatchers.IO) {
            writeJson(session, "folders/${folder.cacheKey()}.json", folderPageToJson(page))
        }

    suspend fun folderSyncedAt(session: AuthSession, folder: MailFolderEntry): Long? =
        withContext(Dispatchers.IO) {
            readJson(session, "folders/${folder.cacheKey()}.json")?.optLong("syncedAt")?.takeIf { it > 0L }
        }

    suspend fun loadMessageDetail(session: AuthSession, messageId: Int): MailMessageDetail? =
        withContext(Dispatchers.IO) {
            readJson(session, "messages/$messageId.json")?.let(::parseMessageDetail)
        }

    suspend fun isMessageDetailCached(session: AuthSession, messageId: Int): Boolean =
        withContext(Dispatchers.IO) {
            val file = File(accountDir(session), "messages/$messageId.json")
            if (!file.exists()) return@withContext false
            runCatching {
                parseMessageDetail(JSONObject(file.readText())).hasBodyContent()
            }.getOrDefault(false)
        }

    suspend fun saveMessageDetail(session: AuthSession, detail: MailMessageDetail) =
        withContext(Dispatchers.IO) {
            writeJson(session, "messages/${detail.id}.json", messageDetailToJson(detail))
        }

    suspend fun removeMessage(session: AuthSession, messageId: Int) = withContext(Dispatchers.IO) {
        File(accountDir(session), "messages/$messageId.json").delete()
    }

    suspend fun updateMessageInFolders(session: AuthSession, messageId: Int, updater: (MailMessage) -> MailMessage) =
        withContext(Dispatchers.IO) {
            val foldersDir = File(accountDir(session), "folders")
            if (!foldersDir.exists()) return@withContext
            foldersDir.listFiles()?.forEach { file ->
                if (!file.name.endsWith(".json")) return@forEach
                runCatching {
                    val json = JSONObject(file.readText())
                    val messages = json.optJSONArray("messages") ?: return@forEach
                    var changed = false
                    for (i in 0 until messages.length()) {
                        val obj = messages.optJSONObject(i) ?: continue
                        if (obj.optInt("id") != messageId) continue
                        val updated = updater(parseMessage(obj))
                        messages.put(i, messageToJson(updated))
                        changed = true
                    }
                    if (changed) file.writeText(json.toString())
                }
            }
        }

    suspend fun removeMessageFromFolders(session: AuthSession, messageId: Int) = withContext(Dispatchers.IO) {
        val foldersDir = File(accountDir(session), "folders")
        if (!foldersDir.exists()) return@withContext
        foldersDir.listFiles()?.forEach { file ->
            if (!file.name.endsWith(".json")) return@forEach
            runCatching {
                val json = JSONObject(file.readText())
                val messages = json.optJSONArray("messages") ?: return@forEach
                val filtered = JSONArray()
                for (i in 0 until messages.length()) {
                    val obj = messages.optJSONObject(i) ?: continue
                    if (obj.optInt("id") != messageId) filtered.put(obj)
                }
                if (filtered.length() != messages.length()) {
                    json.put("messages", filtered)
                    file.writeText(json.toString())
                }
            }
        }
    }

    private fun accountDir(session: AuthSession): File {
        val key = accountKey(session)
        return File(rootDir, key).also { it.mkdirs() }
    }

    private fun accountKey(session: AuthSession): String {
        val raw = "${session.serverUrl.trimEnd('/')}|${session.username.trim().lowercase()}"
        val digest = MessageDigest.getInstance("SHA-256").digest(raw.toByteArray())
        return digest.joinToString("") { "%02x".format(it) }
    }

    private fun readJson(session: AuthSession, relativePath: String): JSONObject? {
        val file = File(accountDir(session), relativePath)
        if (!file.exists()) return null
        return runCatching { JSONObject(file.readText()) }.getOrNull()
    }

    private fun writeJson(session: AuthSession, relativePath: String, json: JSONObject) {
        val file = File(accountDir(session), relativePath)
        file.parentFile?.mkdirs()
        file.writeText(json.toString())
    }

    private fun bootstrapToJson(bootstrap: MailBootstrap): JSONObject =
        JSONObject()
            .put("accounts", accountsToJson(bootstrap.accounts))
            .put("folders", foldersToJson(bootstrap.folders))
            .put(
                "selectedFolder",
                bootstrap.selectedFolder?.let { folderToJson(it) },
            )
            .put("syncedAt", System.currentTimeMillis())

    private fun parseBootstrap(json: JSONObject): MailBootstrap {
        val accounts = parseAccounts(json.optJSONArray("accounts"))
        val folders = parseFolders(json.optJSONArray("folders"))
        val selected = json.optJSONObject("selectedFolder")?.let(::parseFolder)
        return MailBootstrap(accounts, emptyList(), folders, selected)
    }

    private fun folderPageToJson(page: MailMessagesPage): JSONObject =
        JSONObject()
            .put("messages", messagesToJson(page.messages))
            .put("nextCursor", page.nextCursor ?: JSONObject.NULL)
            .put("syncedAt", System.currentTimeMillis())

    private fun parseFolderPage(json: JSONObject): MailMessagesPage =
        MailMessagesPage(
            messages = parseMessages(json.optJSONArray("messages")),
            // org.json может вернуть число как Integer или Long — берём через Number.toLong().
            nextCursor = (json.opt("nextCursor").takeUnless { it == JSONObject.NULL } as? Number)?.toLong(),
        )

    private fun messageDetailToJson(detail: MailMessageDetail): JSONObject =
        JSONObject()
            .put("id", detail.id)
            .put("subject", detail.subject)
            .put("from", detail.from)
            .put("fromEmail", detail.fromEmail)
            .put("to", detail.to)
            .put("cc", detail.cc)
            .put("dateInt", detail.dateInt)
            .put("bodyHtml", detail.bodyHtml)
            .put("bodyPlain", detail.bodyPlain)
            .put("hasHtmlBody", detail.hasHtmlBody)
            .put("flags", flagsToJson(detail.flags))
            .put("attachments", attachmentsToJson(detail.attachments))
            .put("syncedAt", System.currentTimeMillis())

    private fun parseMessageDetail(json: JSONObject): MailMessageDetail =
        MailMessageDetail(
            id = json.optInt("id"),
            subject = json.optString("subject"),
            from = json.optString("from"),
            fromEmail = json.optString("fromEmail"),
            to = json.optString("to"),
            cc = json.optString("cc"),
            dateInt = json.optLong("dateInt"),
            bodyHtml = json.optString("bodyHtml"),
            bodyPlain = json.optString("bodyPlain"),
            hasHtmlBody = json.optBoolean("hasHtmlBody"),
            flags = parseFlags(json.optJSONObject("flags")),
            attachments = parseAttachments(json.optJSONArray("attachments")),
        )

    private fun accountsToJson(accounts: List<MailAccount>): JSONArray =
        JSONArray().apply {
            accounts.forEach { account ->
                put(
                    JSONObject()
                        .put("id", account.id)
                        .put("email", account.email)
                        .put("name", account.name)
                        .put("draftsMailboxId", account.draftsMailboxId ?: JSONObject.NULL)
                        .put("sentMailboxId", account.sentMailboxId ?: JSONObject.NULL)
                        .put("trashMailboxId", account.trashMailboxId ?: JSONObject.NULL)
                        .put("archiveMailboxId", account.archiveMailboxId ?: JSONObject.NULL)
                        .put("junkMailboxId", account.junkMailboxId ?: JSONObject.NULL),
                )
            }
        }

    private fun parseAccounts(array: JSONArray?): List<MailAccount> {
        if (array == null) return emptyList()
        return buildList {
            for (i in 0 until array.length()) {
                val obj = array.optJSONObject(i) ?: continue
                add(
                    MailAccount(
                        id = obj.optInt("id"),
                        email = obj.optString("email"),
                        name = obj.optString("name"),
                        draftsMailboxId = obj.opt("draftsMailboxId").nullInt(),
                        sentMailboxId = obj.opt("sentMailboxId").nullInt(),
                        trashMailboxId = obj.opt("trashMailboxId").nullInt(),
                        archiveMailboxId = obj.opt("archiveMailboxId").nullInt(),
                        junkMailboxId = obj.opt("junkMailboxId").nullInt(),
                    ),
                )
            }
        }
    }

    private fun foldersToJson(folders: List<MailFolderEntry>): JSONArray =
        JSONArray().apply { folders.forEach { put(folderToJson(it)) } }

    private fun folderToJson(folder: MailFolderEntry): JSONObject =
        JSONObject()
            .put("mailboxId", folder.mailboxId)
            .put("accountId", folder.accountId)
            .put("title", folder.title)
            .put("specialRole", folder.specialRole ?: JSONObject.NULL)
            .put("filter", folder.filter.name)
            .put("unread", folder.unread)

    private fun parseFolders(array: JSONArray?): List<MailFolderEntry> {
        if (array == null) return emptyList()
        return buildList {
            for (i in 0 until array.length()) {
                array.optJSONObject(i)?.let { add(parseFolder(it)) }
            }
        }
    }

    private fun parseFolder(obj: JSONObject): MailFolderEntry =
        MailFolderEntry(
            mailboxId = obj.optInt("mailboxId"),
            accountId = obj.optInt("accountId"),
            title = obj.optString("title"),
            specialRole = obj.opt("specialRole").nullString(),
            filter = runCatching { MailListFilter.valueOf(obj.optString("filter")) }
                .getOrDefault(MailListFilter.ALL),
            unread = obj.optInt("unread"),
        )

    private fun messagesToJson(messages: List<MailMessage>): JSONArray =
        JSONArray().apply { messages.forEach { put(messageToJson(it)) } }

    private fun messageToJson(message: MailMessage): JSONObject =
        JSONObject()
            .put("id", message.id)
            .put("subject", message.subject)
            .put("from", message.from)
            .put("fromEmail", message.fromEmail)
            .put("preview", message.preview)
            .put("dateInt", message.dateInt)
            .put("flags", flagsToJson(message.flags))
            .put("tags", tagsToJson(message.tags))

    private fun tagsToJson(tags: List<MailTag>): JSONArray =
        JSONArray().apply {
            tags.forEach { tag ->
                put(
                    JSONObject()
                        .put("id", tag.id)
                        .put("displayName", tag.displayName)
                        .put("colorHex", tag.colorHex)
                        .put("imapLabel", tag.imapLabel),
                )
            }
        }

    private fun parseTags(array: JSONArray?): List<MailTag> {
        if (array == null) return emptyList()
        return buildList {
            for (i in 0 until array.length()) {
                val obj = array.optJSONObject(i) ?: continue
                val name = obj.optString("displayName").trim()
                if (name.isEmpty()) continue
                add(
                    MailTag(
                        id = obj.optLong("id"),
                        displayName = name,
                        colorHex = obj.optString("colorHex"),
                        imapLabel = obj.optString("imapLabel"),
                    ),
                )
            }
        }
    }

    private fun parseMessages(array: JSONArray?): List<MailMessage> {
        if (array == null) return emptyList()
        return buildList {
            for (i in 0 until array.length()) {
                array.optJSONObject(i)?.let { add(parseMessage(it)) }
            }
        }
    }

    private fun parseMessage(obj: JSONObject): MailMessage =
        MailMessage(
            id = obj.optInt("id"),
            subject = obj.optString("subject"),
            from = obj.optString("from"),
            fromEmail = obj.optString("fromEmail"),
            preview = obj.optString("preview"),
            dateInt = obj.optLong("dateInt"),
            flags = parseFlags(obj.optJSONObject("flags")),
            tags = parseTags(obj.optJSONArray("tags")),
        )

    private fun flagsToJson(flags: MailMessageFlags): JSONObject =
        JSONObject()
            .put("seen", flags.seen)
            .put("flagged", flags.flagged)
            .put("hasAttachments", flags.hasAttachments)
            .put("answered", flags.answered)
            .put("important", flags.important)

    private fun parseFlags(obj: JSONObject?): MailMessageFlags =
        MailMessageFlags(
            seen = obj?.optBoolean("seen", true) ?: true,
            flagged = obj?.optBoolean("flagged", false) ?: false,
            hasAttachments = obj?.optBoolean("hasAttachments", false) ?: false,
            answered = obj?.optBoolean("answered", false) ?: false,
            important = obj?.optBoolean("important", false) ?: false,
        )

    private fun attachmentsToJson(attachments: List<MailAttachment>): JSONArray =
        JSONArray().apply {
            attachments.forEach { attachment ->
                put(
                    JSONObject()
                        .put("id", attachment.id)
                        .put("fileName", attachment.fileName)
                        .put("mime", attachment.mime)
                        .put("size", attachment.size)
                        .put("cid", attachment.cid)
                        .put("downloadUrl", attachment.downloadUrl),
                )
            }
        }

    private fun parseAttachments(array: JSONArray?): List<MailAttachment> {
        if (array == null) return emptyList()
        return buildList {
            for (i in 0 until array.length()) {
                val obj = array.optJSONObject(i) ?: continue
                add(
                    MailAttachment(
                        id = obj.optString("id"),
                        fileName = obj.optString("fileName"),
                        mime = obj.optString("mime"),
                        size = obj.optLong("size"),
                        cid = obj.optString("cid").ifBlank { null },
                        downloadUrl = obj.optString("downloadUrl").ifBlank { null },
                    ),
                )
            }
        }
    }

    private fun Any?.nullInt(): Int? =
        if (this == null || this == JSONObject.NULL) null else (this as? Number)?.toInt()

    private fun Any?.nullString(): String? =
        if (this == null || this == JSONObject.NULL) null else this.toString()
}
