package net.taehui.twilight.site

import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame
import io.netty.handler.codec.http.websocketx.WebSocketFrame
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler.HandshakeComplete
import net.taehui.twilight.Avatar
import net.taehui.twilight.JSON
import net.taehui.twilight.Utility
import net.taehui.twilight.qwilight.QwilightAvatar
import net.taehui.twilight.system.AvatarHandler
import net.taehui.twilight.system.Configure
import net.taehui.twilight.system.AvatarIPSystem
import net.taehui.twilight.system.SiteHandler
import org.apache.commons.lang3.RandomStringUtils
import org.slf4j.LoggerFactory
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.UnknownHostException
import java.nio.file.Files
import java.time.LocalDateTime
import java.util.*


class SiteAvatar : SimpleChannelInboundHandler<WebSocketFrame>(), Avatar {
    override val availableCSX = Any()
    override val siteYellMillis = LinkedList<Long>()
    override var lastSignInDate: LocalDateTime = LocalDateTime.MIN
    override lateinit var handler: ChannelHandlerContext
    override var isAwilight = false
    override var isAvailable = true
    override lateinit var avatarIP: InetAddress
    override var isEstablished = false
    override var isSignedIn = false
    override var isValve = false
    override var qwilightHash = ""
    override var qwilightDate = ""
    override var language = "ko-KR"
    override var qwilightID = ""
    override var qwilightName = ""
    override var avatarID = ""
    override var avatarName = ""
    override var avatarEstimatedID = ""
    override var avatarCompetence = 1
    override var situationValue = QwilightAvatar.NOTE_FILE_MODE
    override var situationText = ""
    private val fragments = mutableListOf<ByteBuf>()
    override val isEnterQuitAware = false
    override val avatarNameTitle = "🌐 "

    override fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString?): ChannelFuture {
        return handler.writeAndFlush(
            BinaryWebSocketFrame(
                Unpooled.wrappedBuffer(
                    EventOuterClass.Event.newBuilder().apply {
                        this.eventID = eventID
                        if (text != null) {
                            this.text = if (text is String) text else ObjectMapper().writeValueAsString(text)
                        }
                        this.addAllData(data.toList())
                    }.build().toByteArray()
                )
            )
        )
    }

    override fun send(event: EventOuterClass.Event) {
        handler.writeAndFlush(event)
    }

    public override fun channelRead0(ctx: ChannelHandlerContext, msg: WebSocketFrame) {
        val fragmentContents = msg.content()
        fragmentContents.retain()
        fragments.add(fragmentContents)
        if (msg.isFinalFragment) {
            val event = EventOuterClass.Event.parseFrom(Unpooled.wrappedBuffer(*fragments.toTypedArray()).nioBuffer())
            fragments.forEach { it.release() }
            fragments.clear()
            doIfValid(event) {
                doIfAvailable {
                    logEvent(event)
                    val jm = ObjectMapper()
                    val eventText = event.text
                    when (event.eventID) {
                        EventOuterClass.Event.EventID.ESTABLISH -> wantNotEstablished {
                            val text = jm.readValue(eventText, JSON.QwilightEstablish::class.java)
                            language = text.language
                            AvatarHandler.establish(
                                this,
                                "*" + RandomStringUtils.randomAlphanumeric(19),
                                remote,
                                isValve = false,
                                isPause = false
                            )
                        }

                        EventOuterClass.Event.EventID.SIGN_IN -> wantNotSignedIn {
                            if (allowSignIn()) {
                                trySignInDate()
                                try {
                                    val text = jm.readValue(eventText, JSON.QwilightSignIn::class.java)
                                    handleSignIn(this, text.avatarID, text.avatarCipher).thenAccept {
                                        if (it != null) {
                                            val avatarID = it[1] as String
                                            AvatarHandler.handleSignIn(
                                                this,
                                                (it[0] as String),
                                                "@$avatarID",
                                                (it[2] as String),
                                                it[3] as Int
                                            )
                                            AvatarIPSystem.putAvatarIP(this, avatarID)
                                        }
                                    }
                                } catch (e: JsonProcessingException) {
                                    handleSignIn(this, eventText).thenAccept {
                                        if (it != null) {
                                            val avatarID = it[0] as String
                                            AvatarHandler.handleSignIn(
                                                this,
                                                eventText,
                                                "@$avatarID",
                                                it[1] as String,
                                                it[2] as Int
                                            )
                                            AvatarIPSystem.putAvatarIP(this, avatarID)
                                        }
                                    }
                                }
                            }
                        }

                        EventOuterClass.Event.EventID.NOT_SIGN_IN -> wantSignedIn { handleNotSignIn() }
                        EventOuterClass.Event.EventID.SITE_YELL -> wantEstablished {
                            SiteHandler.putSiteYell(this, jm.readValue(eventText, JSON.QwilightSiteYell::class.java))
                        }

                        EventOuterClass.Event.EventID.GET_SITE_YELLS -> wantEstablished {
                            val text = jm.readValue(eventText, JSON.QwilightGetSiteYells::class.java)
                            SiteHandler.getSiteYells(UUID.fromString(text.siteID), this, text)
                        }

                        EventOuterClass.Event.EventID.SET_SITE_HAND -> wantEstablished {
                            val text = jm.readValue(eventText, JSON.QwilightSetSiteHand::class.java)
                            AvatarHandler.getAvatar(text.avatarID)?.let {
                                SiteHandler.setSiteHand(
                                    this, UUID.fromString(text.siteID), it
                                )
                            }
                        }

                        EventOuterClass.Event.EventID.EXILE_AVATAR -> wantEstablished {
                            val text = jm.readValue(eventText, JSON.QwilightExileAvatar::class.java)
                            AvatarHandler.getAvatar(text.avatarID)?.let {
                                SiteHandler.exileAvatar(
                                    UUID.fromString(text.siteID),
                                    this,
                                    it
                                )
                            }
                        }

                        EventOuterClass.Event.EventID.SET_SITE_NAME -> wantEstablished {
                            val text = jm.readValue(eventText, JSON.QwilightSetSiteName::class.java)
                            setSiteName(text.siteID, text.siteName)
                        }

                        EventOuterClass.Event.EventID.ENTER_SITE -> wantEstablished {
                            val text = jm.readValue(eventText, JSON.QwilightEnterSite::class.java)
                            SiteHandler.enterSite(SiteHandler.getSiteID(text.siteID), this, text.siteCipher)
                        }

                        EventOuterClass.Event.EventID.QUIT_SITE -> wantEstablished {
                            SiteHandler.quitAvatar(
                                this, UUID.fromString(eventText)
                            )
                        }

                        EventOuterClass.Event.EventID.NEW_SITE -> wantEstablished {
                            doNewSite(jm.readValue(eventText, JSON.QwilightNewSite::class.java))
                        }

                        EventOuterClass.Event.EventID.NEW_SILENT_SITE -> wantEstablished {
                            doNewSilentSite(
                                eventText
                            )
                        }

                        EventOuterClass.Event.EventID.POST_FILE -> wantEstablished {
                            logFuture {
                                val data = event.getData(0).toByteArray()
                                val tmpPath = Configure.path.tmpPath.resolve(
                                    "${RandomStringUtils.randomAlphanumeric(8)}.$eventText"
                                )
                                Files.write(Configure.path.wwwPath.resolve(tmpPath), data)
                                send(
                                    EventOuterClass.Event.EventID.POST_FILE, "${Configure.www.remote}/${
                                        tmpPath.toString().replace(" ".toRegex(), "%20")
                                    }"
                                )
                            }
                        }

                        else -> Unit
                    }
                }
            }
        }
    }

    override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
        if (evt is HandshakeComplete) {
            handler = ctx
            avatarIP = try {
                InetAddress.getByName(evt.requestHeaders()["X-Real-IP"])
            } catch (e: UnknownHostException) {
                (handler.channel().remoteAddress() as InetSocketAddress).address
            }
        }
    }

    override fun toString(): String {
        return "[$loggerID] Idle"
    }

    override fun channelInactive(ctx: ChannelHandlerContext) {
        AvatarHandler.quitAvatar(avatarID)
    }

    override val loggerID: String
        get() = if (isEstablished) {
            if (isSignedIn) {
                "{ID: $avatarID, Name: $avatarName, IP: $remote}"
            } else {
                if (avatarEstimatedID.isNotEmpty()) {
                    "{ID: $avatarID ($avatarEstimatedID?), Name: $avatarName, IP: $remote}"
                } else {
                    "{ID: $avatarID, Name: $avatarName, IP: $remote}"
                }
            }
        } else {
            "{IP: $remote}}"
        }

    override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
        logFault(cause)
    }

    override fun logInfo(toNotify: String) {
        LoggerFactory.getLogger(javaClass).info("[{}] {}", loggerID, toNotify)
    }

    override fun logFault(e: Throwable) {
        if (Utility.isValidFault(e)) {
            LoggerFactory.getLogger(javaClass).error("[{}] {}", loggerID, Utility.getFaultText(e))
        }
    }
}