package su.reya.coop.nostr

import rust.nostr.sdk.AckPolicy
import rust.nostr.sdk.Client
import rust.nostr.sdk.EventBuilder
import rust.nostr.sdk.Filter
import rust.nostr.sdk.Kind
import rust.nostr.sdk.KindStandard
import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.RelayCapabilities
import rust.nostr.sdk.RelayMetadata
import rust.nostr.sdk.RelayStatus
import rust.nostr.sdk.RelayUrl
import rust.nostr.sdk.ReqExitPolicy
import rust.nostr.sdk.ReqTarget
import rust.nostr.sdk.SendEventTarget
import rust.nostr.sdk.SubscribeAutoCloseOptions
import rust.nostr.sdk.extractRelayList
import rust.nostr.sdk.nip17ExtractRelayList
import kotlin.time.Duration

class RelayManager(private val nostr: Nostr) {
    companion object {
        val BOOTSTRAP_RELAYS = listOf(
            "wss://relay.ditto.pub",
            "wss://relay.primal.net",
            "wss://relay.nostr.net",
            "wss://profiles.nostr1.com",
        )

        val INDEXER_RELAY = listOf(
            "wss://indexer.coracle.social",
            "wss://user.kindpag.es",
        )

        val ALL_RELAYS = BOOTSTRAP_RELAYS + INDEXER_RELAY
    }

    private val client: Client? get() = nostr.client
    private val signer: UniversalSigner get() = nostr.signer

    suspend fun connectBootstrapRelays() {
        BOOTSTRAP_RELAYS.forEach { url ->
            client?.addRelay(RelayUrl.parse(url))
        }
        INDEXER_RELAY.forEach { url ->
            client?.addRelay(
                url = RelayUrl.parse(url),
                capabilities = RelayCapabilities.gossip()
            )
        }
        // Connect to all bootstrap relays
        client?.connect()
    }

    suspend fun reconnect() {
        ALL_RELAYS.forEach { url ->
            try {
                client?.relay(RelayUrl.parse(url)).let { relay ->
                    if (relay != null) {
                        if (relay.status() != RelayStatus.CONNECTED) {
                            relay.connect()
                        }
                    }
                }
            } catch (e: Exception) {
                println("Failed to reconnect relay: ${e.message}")
            }
        }
    }

    suspend fun disconnect() {
        ALL_RELAYS.forEach { url ->
            try {
                client?.disconnectRelay(RelayUrl.parse(url))
            } catch (e: Exception) {
                println("Failed to disconnect relay: ${e.message}")
            }
        }
    }

    internal suspend fun getDefaultRelayList(): Map<RelayUrl, RelayMetadata> {
        // Construct a list of relays
        val relayList = mapOf(
            RelayUrl.parse("wss://relay.damus.io") to RelayMetadata.READ,
            RelayUrl.parse("wss://relay.primal.net") to RelayMetadata.READ,
            RelayUrl.parse("wss://relay.nostr.net") to RelayMetadata.WRITE,
            RelayUrl.parse("wss://nostr.superfriends.online") to RelayMetadata.WRITE
        )

        // Ensure all relays are added and connected
        relayList.forEach { (relay, metadata) ->
            client?.addRelay(
                url = relay,
                capabilities =
                    when (metadata) {
                        RelayMetadata.READ -> RelayCapabilities.read()
                        RelayMetadata.WRITE -> RelayCapabilities.write()
                    }
            )
            client?.connectRelay(relay)
        }

        return relayList
    }

    internal suspend fun getDefaultMsgRelayList(): List<RelayUrl> {
        // Construct a list of messaging relays
        val msgRelayList = listOf(
            RelayUrl.parse("wss://auth.nostr1.com"),
            RelayUrl.parse("wss://nip17.com"),
        )

        // Ensure all relays are added and connected
        msgRelayList.forEach { relay ->
            client?.addRelay(relay, RelayCapabilities.none())
            client?.connectRelay(relay)
        }

        return msgRelayList
    }

    suspend fun getRelayList(publicKey: PublicKey): Map<RelayUrl, RelayMetadata?> {
        try {
            val kind = Kind.fromStd(KindStandard.RELAY_LIST)
            val filter = Filter().kind(kind).author(publicKey).limit(1u)
            val events = client?.database()?.query(filter)

            return extractRelayList(events?.toVec()?.firstOrNull() ?: return emptyMap())
        } catch (e: Exception) {
            throw IllegalStateException("Failed to get relay list: ${e.message}", e)
        }
    }

    suspend fun setRelayList(relays: Map<RelayUrl, RelayMetadata?>) {
        try {
            val event = EventBuilder.relayList(relays).finalizeAsync(signer)

            client?.sendEvent(
                event = event,
                target = SendEventTarget.broadcast(),
                ackPolicy = AckPolicy.none(),
            )
        } catch (e: Exception) {
            throw IllegalStateException("Failed to set msg relays: ${e.message}", e)
        }
    }

    suspend fun setMsgRelays(urls: List<RelayUrl>) {
        try {
            val event = EventBuilder.nip17RelayList(urls).finalizeAsync(signer)

            client?.sendEvent(
                event = event,
                target = SendEventTarget.toNip65(),
                ackPolicy = AckPolicy.none(),
            )

            val currentUser =
                signer.getPublicKeyAsync() ?: throw IllegalStateException("User not signed in")

            val kind = Kind.fromStd(KindStandard.INBOX_RELAYS)
            val filter = Filter().kind(kind).author(currentUser).limit(1u)

            val target = ReqTarget.auto(listOf(filter))
            val opts = SubscribeAutoCloseOptions().exitPolicy(ReqExitPolicy.ExitOnEose)

            client?.subscribe(target = target, closeOn = opts)
        } catch (e: Exception) {
            throw IllegalStateException("Failed to set msg relays: ${e.message}", e)
        }
    }

    suspend fun getMsgRelays(publicKey: PublicKey): List<RelayUrl> {
        try {
            val kind = Kind.fromStd(KindStandard.INBOX_RELAYS)
            val filter = Filter().kind(kind).author(publicKey).limit(1u)
            val events = client?.database()?.query(filter)
            val event = events?.first() ?: return emptyList()

            return nip17ExtractRelayList(event)
        } catch (e: Exception) {
            throw IllegalStateException("Failed to get msg relays: ${e.message}", e)
        }
    }

    suspend fun fetchMsgRelays(publicKey: PublicKey): List<RelayUrl> {
        try {
            val kind = Kind.fromStd(KindStandard.INBOX_RELAYS)
            val filter = Filter().kind(kind).author(publicKey).limit(1u)
            val target = ReqTarget.auto(listOf(filter))
            val events = client?.fetchEvents(target, timeout = Duration.parse("3s"))

            return nip17ExtractRelayList(events?.toVec()?.firstOrNull() ?: return emptyList())
        } catch (e: Exception) {
            throw IllegalStateException("Failed to fetch msg relays: ${e.message}", e)
        }
    }
}