package net.taehui.twilight

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelHandlerContext
import net.taehui.twilight.qwilight.QwilightAvatar
import net.taehui.twilight.system.*
import org.apache.hc.client5.http.HttpResponseException
import org.apache.hc.client5.http.classic.methods.HttpPatch
import org.apache.hc.client5.http.classic.methods.HttpPost
import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler
import org.apache.hc.client5.http.impl.classic.HttpClients
import org.apache.hc.core5.http.ContentType
import org.apache.hc.core5.http.io.entity.StringEntity
import java.net.InetAddress
import java.time.LocalDateTime
import java.util.*
import java.util.concurrent.CompletableFuture

interface Avatar : AutoCloseable, Logger {
    val availableCSX: Any
    val siteYellMillis: LinkedList<Long>
    var lastSignInDate: LocalDateTime
    var handler: ChannelHandlerContext
    var isAwilight: Boolean
    var isAvailable: Boolean
    val isEnterQuitAware: Boolean
    var isSignedIn: Boolean
    var isEstablished: Boolean
    var avatarID: String
    var avatarName: String
    var avatarEstimatedID: String
    var avatarIP: InetAddress
    var language: String
    var situationValue: Int
    var situationText: String
    var isValve: Boolean
    var avatarCompetence: Int
    var qwilightHash: String
    var qwilightDate: String
    var qwilightID: String
    var qwilightName: String
    val avatarNameTitle: String
    val loggerID: String

    fun handleSignIn(avatar: Avatar, avatarID: String, avatarCipher: String): CompletableFuture<Array<Any>?> {
        return logValueFuture {
            HttpClients.createDefault().use {
                val jm = ObjectMapper()
                val dataPost = HttpPost(Configure.www.taehui + "/avatar/getTotem")
                dataPost.setHeader("millis", System.currentTimeMillis())
                dataPost.setHeader("X-Real-IP", avatar.remote)
                dataPost.entity = StringEntity(
                    jm.writeValueAsString(
                        object {
                            val avatarID = avatarID
                            val avatarCipher = avatarCipher
                        }), ContentType.APPLICATION_JSON
                )
                try {
                    val taehuiGetTotem = jm.readValue(
                        it.execute(dataPost, BasicHttpClientResponseHandler()),
                        JSON.TaehuiGetTotem::class.java
                    )
                    arrayOf(
                        taehuiGetTotem.totem,
                        taehuiGetTotem.avatarID,
                        taehuiGetTotem.avatarName,
                        taehuiGetTotem.level
                    )
                } catch (e: HttpResponseException) {
                    avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("wrongAvatar"))
                    null
                }
            }
        }
    }

    fun handleSignIn(avatar: Avatar, totem: String): CompletableFuture<Array<Any>?> {
        return logValueFuture {
            HttpClients.createDefault().use {
                val data = HttpPatch(Configure.www.taehui + "/avatar/totem")
                data.setHeader("X-Real-IP", avatar.remote)
                data.setHeader("millis", System.currentTimeMillis())
                data.setHeader("totem", totem)
                try {
                    val taehuiTotem = ObjectMapper().readValue(
                        it.execute(data, BasicHttpClientResponseHandler()), JSON.TaehuiTotem::class.java
                    )
                    arrayOf(taehuiTotem.avatarID, taehuiTotem.avatarName, taehuiTotem.level)
                } catch (e: HttpResponseException) {
                    avatar.send(EventOuterClass.Event.EventID.FAILED_VALIDATE_TOTEM, null)
                    null
                }
            }
        }
    }

    fun doIfValid(event: EventOuterClass.Event, onValid: () -> Unit) {
        val millis = System.currentTimeMillis()
        if (millis < 10 * 60 * 1000 + event.millis) {
            if (event.eventID == EventOuterClass.Event.EventID.ESTABLISH || avatarID == event.avatarID) {
                onValid()
            } else {
                logInfo("$avatarID != ${event.avatarID}")
            }
        } else {
            logInfo("$millis << ${event.millis}")
        }
    }

    fun doIfAvailable(onAvailable: () -> Unit) {
        synchronized(availableCSX) {
            if (isAvailable) {
                onAvailable()
            }
        }
    }

    fun wantSignedIn(toHandle: (String) -> Unit) {
        wantEstablished {
            if (isSignedIn) {
                toHandle(it)
            }
        }
    }

    fun wantNotSignedIn(toHandle: () -> Unit) {
        wantEstablished {
            if (!isSignedIn) {
                toHandle()
            }
        }
    }

    fun wantEstablished(toHandle: (String) -> Unit) {
        if (isEstablished) {
            toHandle(avatarID)
        }
    }

    fun wantNotEstablished(toHandle: () -> Unit) {
        if (!isEstablished) {
            toHandle()
        }
    }

    val remote: String
        get() = avatarIP.hostAddress

    fun translateLanguage(target: String): String {
        return LanguageSystem.getLanguage(language, target)
    }

    val isSU: Boolean
        get() = avatarCompetence == 2

    fun isMillisSuitable(targetMillis: Long): Boolean {
        if (siteYellMillis.size >= 5) {
            val millis = siteYellMillis.poll()
            siteYellMillis.offer(targetMillis)
            return targetMillis - millis >= 5000
        }
        siteYellMillis.offer(targetMillis)
        return true
    }

    fun allowSignIn(): Boolean {
        return LocalDateTime.now().minusSeconds(1).isAfter(lastSignInDate)
    }

    fun trySignInDate() {
        lastSignInDate = LocalDateTime.now()
    }

    fun handleNotSignIn() {
        if (isSignedIn) {
            SiteHandler.quitAvatar(this)
            isSignedIn = false
            avatarID = qwilightID
            avatarName = avatarNameTitle + qwilightName
            avatarCompetence = 1
            setLastDate()
            send(EventOuterClass.Event.EventID.NOT_SIGN_IN, object {
                val avatarID = this@Avatar.avatarID
                val avatarName = this@Avatar.avatarName
            })
        }
    }

    fun setLastDate() {
        if (isSignedIn && !isAwilight) {
            DB.setLastDate(Utility.getDefaultAvatarID(avatarID))
        }
    }

    fun doNewSite(siteData: JSON.QwilightNewSite) {
        if (siteData.siteName.isNotEmpty() && !siteData.siteName.startsWith("@")) {
            val siteID = UUID.randomUUID()
            if (siteData.isNetSite) {
                SiteHandler.putNetSite(this, siteID, siteData)
            } else {
                SiteHandler.putSite(this, siteID, siteData)
            }
        } else {
            send(EventOuterClass.Event.EventID.WARNING, translateLanguage("siteNameNotValid"))
        }
    }

    fun doNewSilentSite(targetAvatarID: String) {
        if (Utility.getDefaultAvatarID(avatarID) == Utility.getDefaultAvatarID(
                targetAvatarID
            )
        ) {
            send(EventOuterClass.Event.EventID.WARNING, translateLanguage("silentSiteAvatarIsYou"))
        } else {
            AvatarHandler.getAvatar(targetAvatarID)?.let { avatar ->
                DB.getSilentSiteCompetence(
                    Utility.getDefaultAvatarID(
                        targetAvatarID
                    )
                ).thenAccept {
                    if (isSignedIn) {
                        if (it == QwilightAvatar.SILENT_SITE_CALLABLE || it == QwilightAvatar.SILENT_SITE_AVATAR && isSignedIn || it == QwilightAvatar.SILENT_SITE_UBUNTU && DB.isCrossUbuntu(
                                avatarID, targetAvatarID
                            )
                        ) {
                            SiteHandler.putSilentSite(this, avatar)
                        } else {
                            send(EventOuterClass.Event.EventID.WARNING, translateLanguage("lowerCompetenceAsSilentSite"))
                        }
                    } else {
                        send(EventOuterClass.Event.EventID.WARNING, translateLanguage("silentSiteAvatarIsNotSignedIn"))
                    }
                }
            }
        }
    }

    fun setSiteName(siteID: String, siteName: String) {
        if (siteName.isNotEmpty() && !siteName.startsWith("@")) {
            SiteHandler.setSiteName(this, UUID.fromString(siteID), siteName)
        } else {
            send(EventOuterClass.Event.EventID.WARNING, translateLanguage("siteNameNotValid"))
        }
    }

    fun handleSignIn(totem: String, avatarID: String, avatarName: String, avatarCompetence: Int) {
        if (!isSignedIn) {
            SiteHandler.quitAvatar(this)
            isSignedIn = true
            this.avatarID = avatarID
            this.avatarName = avatarNameTitle + avatarName
            this.avatarCompetence = avatarCompetence
            setLastDate()
            send(EventOuterClass.Event.EventID.SIGN_IN, object {
                val totem = totem
                val avatarID = avatarID
                val avatarName = avatarName
            })
        }
    }

    fun setEstablished(avatarID: String, qwilightName: String, isValve: Boolean) {
        isEstablished = true
        qwilightID = avatarID
        this.qwilightName = qwilightName
        this.isValve = isValve
        this.avatarID = qwilightID
        avatarName = avatarNameTitle + this.qwilightName
        send(EventOuterClass.Event.EventID.ESTABLISH, object {
            val avatarID = avatarID
            val avatarName = this@Avatar.avatarName
        })
    }

    fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString?): ChannelFuture

    fun send(event: EventOuterClass.Event)

    override fun close() {
        handler.close()
    }
}