package com.wisp.app.nostr

import com.wisp.app.relay.RelayConfig
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive

data class MuteList(
    val pubkeys: Set<String> = emptySet(),
    val words: Set<String> = emptySet()
)

data class FollowSet(
    val pubkey: String,
    val dTag: String,
    val name: String,
    val members: Set<String>,
    val createdAt: Long,
    val isPrivate: Boolean = false
)

data class BookmarkList(
    val eventIds: Set<String> = emptySet(),
    val coordinates: Set<String> = emptySet(),
    val hashtags: Set<String> = emptySet(),
    val createdAt: Long = 0
)

data class BookmarkSet(
    val pubkey: String,
    val dTag: String,
    val name: String,
    val eventIds: Set<String>,
    val coordinates: Set<String> = emptySet(),
    val hashtags: Set<String> = emptySet(),
    val createdAt: Long = 0,
    val isPrivate: Boolean = false
)

data class InterestSet(
    val dTag: String,
    val name: String,
    val hashtags: Set<String>,
    val createdAt: Long = 0
)

data class SimpleGroupEntry(
    val groupId: String,
    val relayUrl: String,
    val name: String? = null
)

data class RelaySet(
    val pubkey: String,
    val dTag: String,
    val name: String,
    val relays: Set<String>,
    val createdAt: Long
)

object Nip51 {
    const val KIND_BLOCKED_RELAYS = 10006
    const val KIND_SEARCH_RELAYS = 10007
    const val KIND_MUTE_LIST = 10000
    const val KIND_PIN_LIST = 10001
    const val KIND_BOOKMARK_LIST = 10003
    const val KIND_DM_RELAYS = 10050
    const val KIND_SIMPLE_GROUPS = 10009
    const val KIND_FAVORITE_RELAYS = 10012
    const val KIND_FOLLOW_SET = 30000
    const val KIND_RELAY_SET = 30002
    const val KIND_BOOKMARK_SET = 30003
    const val KIND_INTEREST_SET = 30015

    fun parseRelaySet(event: NostrEvent): List<String> {
        return event.tags.mapNotNull { tag ->
            if (tag.size >= 2 && (tag[0] == "relay" || tag[0] == "r")) {
                val url = tag[1].trim().trimEnd('/')
                if (RelayConfig.isValidUrl(url)) url else null
            } else null
        }
    }

    fun buildRelaySetTags(urls: List<String>): List<List<String>> {
        return urls.map { listOf("relay", it) }
    }

    fun parseRelaySetNamed(event: NostrEvent): RelaySet? {
        if (event.kind != KIND_RELAY_SET) return null
        val dTag = event.tags.firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1) ?: return null
        val relays = mutableSetOf<String>()
        var title: String? = null
        var name: String? = null
        for (tag in event.tags) {
            if (tag.size < 2) continue
            when (tag[0]) {
                "relay", "r" -> {
                    val url = tag[1].trim().trimEnd('/')
                    if (RelayConfig.isValidUrl(url)) relays.add(url)
                }
                "title" -> title = tag[1]
                "name" -> name = tag[1]
            }
        }
        return RelaySet(
            pubkey = event.pubkey,
            dTag = dTag,
            name = title ?: name ?: dTag,
            relays = relays,
            createdAt = event.created_at
        )
    }

    fun buildRelaySetNamedTags(dTag: String, relays: Set<String>, title: String? = null): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        tags.add(listOf("d", dTag))
        if (title != null) tags.add(listOf("title", title))
        for (url in relays) tags.add(listOf("relay", url))
        return tags
    }

    fun parseMuteList(event: NostrEvent): MuteList {
        val pubkeys = mutableSetOf<String>()
        val words = mutableSetOf<String>()
        for (tag in event.tags) {
            if (tag.size < 2) continue
            when (tag[0]) {
                "p" -> pubkeys.add(tag[1])
                "word" -> words.add(tag[1])
            }
        }
        return MuteList(pubkeys, words)
    }

    fun parsePrivateTags(json: String): MuteList {
        val pubkeys = mutableSetOf<String>()
        val words = mutableSetOf<String>()
        try {
            val arr = kotlinx.serialization.json.Json.parseToJsonElement(json).jsonArray
            for (element in arr) {
                val tag = element.jsonArray
                if (tag.size < 2) continue
                when (tag[0].jsonPrimitive.content) {
                    "p" -> pubkeys.add(tag[1].jsonPrimitive.content)
                    "word" -> words.add(tag[1].jsonPrimitive.content)
                }
            }
        } catch (_: Exception) {}
        return MuteList(pubkeys, words)
    }

    fun buildMuteListContent(blockedPubkeys: Set<String>, mutedWords: Set<String>): String {
        val arr = buildJsonArray {
            for (pubkey in blockedPubkeys) {
                add(buildJsonArray { add(JsonPrimitive("p")); add(JsonPrimitive(pubkey)) })
            }
            for (word in mutedWords) {
                add(buildJsonArray { add(JsonPrimitive("word")); add(JsonPrimitive(word)) })
            }
        }
        return arr.toString()
    }

    fun buildMuteListTags(blockedPubkeys: Set<String>, mutedWords: Set<String>): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        for (pubkey in blockedPubkeys) tags.add(listOf("p", pubkey))
        for (word in mutedWords) tags.add(listOf("word", word))
        return tags
    }

    fun buildPrivateContent(tags: List<List<String>>): String {
        val arr = buildJsonArray {
            for (tag in tags) {
                add(buildJsonArray { for (item in tag) add(JsonPrimitive(item)) })
            }
        }
        return arr.toString()
    }

    fun parsePrivateTagsGeneric(json: String): List<List<String>> {
        return try {
            val arr = kotlinx.serialization.json.Json.parseToJsonElement(json).jsonArray
            arr.map { element ->
                element.jsonArray.map { it.jsonPrimitive.content }
            }
        } catch (_: Exception) { emptyList() }
    }

    fun parseFollowSet(event: NostrEvent, decryptedContent: String? = null): FollowSet? {
        if (event.kind != KIND_FOLLOW_SET) return null
        val dTag = event.tags.firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1) ?: return null
        val members = mutableSetOf<String>()
        for (tag in event.tags) {
            if (tag.size >= 2 && tag[0] == "p") members.add(tag[1])
        }
        // Check public tags for title or name (some clients use "name" instead of "title")
        var title = event.tags.firstOrNull { it.size >= 2 && it[0] == "title" }?.get(1)
        var name = event.tags.firstOrNull { it.size >= 2 && it[0] == "name" }?.get(1)
        if (decryptedContent != null) {
            for (tag in parsePrivateTagsGeneric(decryptedContent)) {
                if (tag.size < 2) continue
                when (tag[0]) {
                    "p" -> members.add(tag[1])
                    "title" -> if (title == null) title = tag[1]
                    "name" -> if (name == null) name = tag[1]
                }
            }
        }
        val isPrivate = members.isEmpty() || (event.tags.none { it.size >= 2 && it[0] == "p" } && decryptedContent != null)
        return FollowSet(
            pubkey = event.pubkey,
            dTag = dTag,
            name = title ?: name ?: dTag,
            members = members,
            createdAt = event.created_at,
            isPrivate = isPrivate
        )
    }

    fun buildFollowSetTags(dTag: String, members: Set<String>, title: String? = null): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        tags.add(listOf("d", dTag))
        if (title != null) tags.add(listOf("title", title))
        for (pubkey in members) tags.add(listOf("p", pubkey))
        return tags
    }

    fun buildFollowSetPrivate(dTag: String, members: Set<String>, title: String? = null): Pair<List<List<String>>, String> {
        val tags = listOf(listOf("d", dTag))
        val privateTags = mutableListOf<List<String>>()
        if (title != null) privateTags.add(listOf("title", title))
        for (pubkey in members) privateTags.add(listOf("p", pubkey))
        return Pair(tags, buildPrivateContent(privateTags))
    }

    fun parseBookmarkSet(event: NostrEvent, decryptedContent: String? = null): BookmarkSet? {
        if (event.kind != KIND_BOOKMARK_SET) return null
        val dTag = event.tags.firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1) ?: return null
        val eventIds = mutableSetOf<String>()
        val coordinates = mutableSetOf<String>()
        val hashtags = mutableSetOf<String>()
        var title = event.tags.firstOrNull { it.size >= 2 && it[0] == "title" }?.get(1)
        var name = event.tags.firstOrNull { it.size >= 2 && it[0] == "name" }?.get(1)
        for (tag in event.tags) {
            if (tag.size < 2) continue
            when (tag[0]) {
                "e" -> eventIds.add(tag[1])
                "a" -> coordinates.add(tag[1])
                "t" -> hashtags.add(tag[1])
            }
        }
        val hasPublicItems = eventIds.isNotEmpty() || coordinates.isNotEmpty() || hashtags.isNotEmpty()
        if (decryptedContent != null) {
            for (tag in parsePrivateTagsGeneric(decryptedContent)) {
                if (tag.size < 2) continue
                when (tag[0]) {
                    "e" -> eventIds.add(tag[1])
                    "a" -> coordinates.add(tag[1])
                    "t" -> hashtags.add(tag[1])
                    "title" -> if (title == null) title = tag[1]
                    "name" -> if (name == null) name = tag[1]
                }
            }
        }
        val isPrivate = !hasPublicItems && decryptedContent != null
        return BookmarkSet(
            pubkey = event.pubkey,
            dTag = dTag,
            name = title ?: name ?: dTag,
            eventIds = eventIds,
            coordinates = coordinates,
            hashtags = hashtags,
            createdAt = event.created_at,
            isPrivate = isPrivate
        )
    }

    fun buildBookmarkSetTags(
        dTag: String,
        eventIds: Set<String>,
        coordinates: Set<String> = emptySet(),
        hashtags: Set<String> = emptySet(),
        title: String? = null,
        relayHints: Map<String, String> = emptyMap()
    ): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        tags.add(listOf("d", dTag))
        if (title != null) tags.add(listOf("title", title))
        for (id in eventIds) {
            val hint = relayHints[id]
            if (hint != null) tags.add(listOf("e", id, hint)) else tags.add(listOf("e", id))
        }
        for (coord in coordinates) tags.add(listOf("a", coord))
        for (tag in hashtags) tags.add(listOf("t", tag))
        return tags
    }

    fun buildBookmarkSetPrivate(
        dTag: String,
        eventIds: Set<String>,
        coordinates: Set<String> = emptySet(),
        hashtags: Set<String> = emptySet(),
        title: String? = null,
        relayHints: Map<String, String> = emptyMap()
    ): Pair<List<List<String>>, String> {
        val tags = listOf(listOf("d", dTag))
        val privateTags = mutableListOf<List<String>>()
        if (title != null) privateTags.add(listOf("title", title))
        for (id in eventIds) {
            val hint = relayHints[id]
            if (hint != null) privateTags.add(listOf("e", id, hint)) else privateTags.add(listOf("e", id))
        }
        for (coord in coordinates) privateTags.add(listOf("a", coord))
        for (tag in hashtags) privateTags.add(listOf("t", tag))
        return Pair(tags, buildPrivateContent(privateTags))
    }

    fun parseBookmarkList(event: NostrEvent): BookmarkList {
        val eventIds = mutableSetOf<String>()
        val coordinates = mutableSetOf<String>()
        val hashtags = mutableSetOf<String>()
        for (tag in event.tags) {
            if (tag.size < 2) continue
            when (tag[0]) {
                "e" -> eventIds.add(tag[1])
                "a" -> coordinates.add(tag[1])
                "t" -> hashtags.add(tag[1])
            }
        }
        return BookmarkList(eventIds, coordinates, hashtags, event.created_at)
    }

    fun buildBookmarkListTags(eventIds: Set<String>, coordinates: Set<String> = emptySet(), hashtags: Set<String> = emptySet(), relayHints: Map<String, String> = emptyMap()): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        for (id in eventIds) {
            val hint = relayHints[id]
            if (hint != null) tags.add(listOf("e", id, hint)) else tags.add(listOf("e", id))
        }
        for (coord in coordinates) tags.add(listOf("a", coord))
        for (tag in hashtags) tags.add(listOf("t", tag))
        return tags
    }

    fun parseInterestSet(event: NostrEvent): InterestSet? {
        if (event.kind != KIND_INTEREST_SET) return null
        val dTag = event.tags.firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1) ?: return null
        val hashtags = mutableSetOf<String>()
        var title: String? = null
        var name: String? = null
        for (tag in event.tags) {
            if (tag.size < 2) continue
            when (tag[0]) {
                "t" -> hashtags.add(tag[1].lowercase())
                "title" -> title = tag[1]
                "name" -> name = tag[1]
            }
        }
        return InterestSet(
            dTag = dTag,
            name = title ?: name ?: dTag,
            hashtags = hashtags,
            createdAt = event.created_at
        )
    }

    fun buildInterestSetTags(dTag: String, hashtags: Set<String>, title: String? = null): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        tags.add(listOf("d", dTag))
        if (title != null) tags.add(listOf("title", title))
        for (tag in hashtags) tags.add(listOf("t", tag.lowercase()))
        return tags
    }

    fun parsePinList(event: NostrEvent): Set<String> {
        return event.tags.mapNotNull { tag ->
            if (tag.size >= 2 && tag[0] == "e") tag[1] else null
        }.toSet()
    }

    fun buildPinListTags(eventIds: Set<String>, relayHints: Map<String, String> = emptyMap()): List<List<String>> {
        return eventIds.map { id ->
            val hint = relayHints[id]
            if (hint != null) listOf("e", id, hint) else listOf("e", id)
        }
    }

    fun parseSimpleGroups(event: NostrEvent): List<SimpleGroupEntry> {
        if (event.kind != KIND_SIMPLE_GROUPS) return emptyList()
        return event.tags.mapNotNull { tag ->
            if (tag.size >= 3 && tag[0] == "group") {
                val groupId = tag[1]
                val relayUrl = tag[2].trim().trimEnd('/')
                val name = tag.getOrNull(3)?.takeIf { it.isNotBlank() }
                if (groupId.isNotEmpty() && RelayConfig.isValidUrl(relayUrl)) {
                    SimpleGroupEntry(groupId, relayUrl, name)
                } else null
            } else null
        }
    }

    fun buildSimpleGroupsTags(groups: List<SimpleGroupEntry>): List<List<String>> {
        val tags = mutableListOf<List<String>>()
        val relays = mutableSetOf<String>()
        for (group in groups) {
            val tag = if (group.name != null) {
                listOf("group", group.groupId, group.relayUrl, group.name)
            } else {
                listOf("group", group.groupId, group.relayUrl)
            }
            tags.add(tag)
            relays.add(group.relayUrl)
        }
        for (relay in relays) {
            tags.add(listOf("r", relay))
        }
        return tags
    }
}
