package xyz.maxwellj.chookpen

import io.javalin.Javalin
import io.javalin.websocket.WsContext
import java.util.concurrent.ConcurrentHashMap
import java.util.UUID

import kotlin.concurrent.fixedRateTimer

import java.io.File
import java.io.BufferedReader

import java.math.BigInteger
import java.security.MessageDigest

fun md5(input:String): String {
    val md = MessageDigest.getInstance("MD5")
    return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
}

object WsSessionManager {
    var peopleOnline = mutableListOf("")
    var sessionsList = mutableListOf("")

    private val sessions = ConcurrentHashMap<WsContext, String>()
    private val sessionIds = ConcurrentHashMap<String, WsContext>()

    init {
        fixedRateTimer("websocket-ping", period = 5000) {
            sendPing()
        }
    }
    private fun sendPing() {
        val deadSessions = mutableListOf<WsContext>()

        sessions.keys.forEach { ctx ->
            try {
                if (ctx.session.isOpen) {
                    ctx.send("ping")
                } else {
                    deadSessions.add(ctx)
                }
            } catch (e: Exception) {
                println("Error sending ping: ${e.message}")
                deadSessions.add(ctx)
            }
        }

        // Clean up any dead sessions
        deadSessions.forEach { removeSession(it) }
    }

    fun broadcastOnlineUsers() {
        broadcast("!users:{${peopleOnline.joinToString(",")}}")
    }

    fun handleUserLogin(username: String) {
        peopleOnline += username
        broadcastOnlineUsers()
    }

    fun addSession(ctx: WsContext) {
        try {
            val sessionId = UUID.randomUUID().toString()
            sessionsList += sessionId
            sessions[ctx] = sessionId
            sessionIds[sessionId] = ctx
        } catch (e: Exception) {
            println("Error adding session: ${e.message}")
        }
    }

    fun removeSession(ctx: WsContext) {
        try {
            val sessionId = sessions[ctx]
            if (sessionId != null) {
                peopleOnline.removeAt(sessionsList.indexOf(sessionId))
                sessionsList.removeAt(sessionsList.indexOf(sessionId))
                sessions.remove(ctx)
                sessionIds.remove(sessionId)
                broadcastOnlineUsers()
            }
        } catch (e: Exception) {
            println("Error removing session: ${e.message}")
        }
    }

    fun broadcast(message: String) {
        val deadSessions = mutableListOf<WsContext>()
        
        sessions.keys.forEach { ctx ->
            try {
                if (ctx.session.isOpen) {
                    ctx.send(message)
                } else {
                    deadSessions.add(ctx)
                }
            } catch (e: Exception) {
                println("Error broadcasting to session: ${e.message}")
                deadSessions.add(ctx)
            }
        }
        
        // Clean up any dead sessions
        deadSessions.forEach { removeSession(it) }
    }

    fun getSessionCount(): Int = sessions.size
}

fun extractMessageContent(inputData: String): String {
    var username = ""
    var message = ""
    var dataType = ""
    var isParsingData = 0
    
    for (char in inputData) {
        if (char == ':') {
            isParsingData = 1
        } else if (isParsingData == 1) {
            if (char == '}') {
                isParsingData = 0
                dataType = ""
            } else if (char != '{') {
                if (dataType == "username") {
                    username += char
                } else if (dataType == "message") {
                    message += char
                }
            }
        } else {
            dataType += char
        }
    }
    
    return("$username: $message")
}

fun handleSentMessage(inputData: String): String {
    println("API request recieved: $inputData")
    // Parse data sent to the server by client
    var username = ""
    var token = ""
    var message = ""
    var dataType = ""
    var command = ""
    var commandArg = ""
    var isParsingData = 0
    for (char in inputData) {
        val character = char
        if (character == ':') {
            isParsingData = 1
        } else if (isParsingData == 1) {
            if (character == '}') {
                isParsingData = 0
                dataType = ""
            } else if (character != '{') {
                if (dataType == "username") {
                    username += character
                } else if (dataType == "token") {
                    token += character
                } else if (dataType == "message") {
                    message += character
                } else if (dataType == "command") {
                    command += character
                } else if (dataType == "commandArg") {
                    commandArg += character
                }
            }
        } else {
            dataType += character
        }
    }
    val userDatabaseParser = BufferedReader(File("userDatabase").reader())
    var lineNumber = 1
    var userLine = ""
    
    // Search the user database to find required information about the user
    userDatabaseParser.forEachLine { line ->
        if (line.contains(username)) {
            userLine = line
        }
        lineNumber++
    }
    userDatabaseParser.close()

    if (userLine == "") {
        return("That account does not exist on this server.")
    }
 
    var usernameInDatabase = ""
    var tokenInDatabase = ""
    var saltInDatabase = ""
    var currentStage = 0
    for (char in userLine) {
        if (char == ':') {
            currentStage ++
        }
        if (currentStage == 0) {
            usernameInDatabase += char
        } else if (currentStage == 1) {
            tokenInDatabase += char
        } else if (currentStage == 2) {
            saltInDatabase += char
        }
    }
    tokenInDatabase = tokenInDatabase.replace(":", "")
    saltInDatabase = saltInDatabase.replace(":", "")
    val tokenWithSalt = (md5(token + saltInDatabase))
    /*println(saltInDatabase)
    println(tokenWithSalt)
    if (tokenWithSalt != tokenInDatabase)  {*/
    if (token != tokenInDatabase) {
        return("Invalid token! Please try putting in your password right")
    }
    // Make the message to respond to the client
    val chatHistoryView = File("chatHistory")
    var fullMessage = ""
    if (message != "") {
        fullMessage = "${chatHistoryView.readText()}$username: $message"
        // Add the client's message to the chat history
        val chatHistory = File("chatHistory")
        chatHistory.appendText("$username: $message ${System.lineSeparator()}")
        message = ""
        return("Success")
    } else if (command != "") {
        if (command == "sync") {
            return(chatHistoryView.readText())
        } else if (command == "login") {
            WsSessionManager.handleUserLogin(commandArg)
            return("Login successful")
        }
    } else {
        return("No data provided")
    }
    return("System: Welcome to Chookpen, $username!")
}

fun syncMessages(inputData: String): String {
    println("API request recieved: $inputData")
    // Parse data sent to the server by client
    var username = ""
    var token = ""
    var dataType = ""
    var isParsingData = 0
    for (char in inputData) {
        val character = char
        if (character == ':') {
            isParsingData = 1
        } else if (isParsingData == 1) {
            if (character == '}') {
                isParsingData = 0
                dataType = ""
            } else if (character != '{') {
                if (dataType == "username") {
                    username += character
                } else if (dataType == "token") {
                    token += character
                }
            }
        } else {
            dataType += character
        }
    }
    val userDatabaseParser = BufferedReader(File("userDatabase").reader())
    var lineNumber = 1
    var userLine = ""
    
    // Search the user database to find required information about the user
    userDatabaseParser.forEachLine { line ->
        if (line.contains(username)) {
            userLine = line
        }
        lineNumber++
    }
    userDatabaseParser.close()

    if (userLine == "") {
        return("Account not found")
    }
 
    var usernameInDatabase = ""
    var tokenInDatabase = ""
    var currentStage = 0
    for (char in userLine) {
        if (char == ':') {
            currentStage ++
        }
        if (currentStage == 0) {
            usernameInDatabase += char
        } else if (currentStage == 1) {
            tokenInDatabase += char
        }
    }
    tokenInDatabase = tokenInDatabase.replace(":", "")
    if (token != tokenInDatabase)  {
        return("Invalid token")
    }
    // Send back message history 
    val chatHistoryView = File("chatHistory")
    return(chatHistoryView.readText())
}

fun createAccount(inputData: String): String {
    println("Account creation request recieved: $inputData")
    // Parse data sent to the server by client
    var username = ""
    var token = ""
    var message = ""
    var dataType = ""
    var isParsingData = 0
    for (char in inputData) {
        val character = char
        if (character == ':') {
            isParsingData = 1
        } else if (isParsingData == 1) {
            if (character == '}') {
                isParsingData = 0
                dataType = ""
            } else if (character != '{') {
                if (dataType == "username") {
                    username += character
                } else if (dataType == "token") {
                    token += character
                } else if (dataType == "message") {
                    message += character
                }
            }
        } else {
            dataType += character
        }
    }
    val userDatabaseParser = BufferedReader(File("userDatabase").reader())
    var lineNumber = 1
    var userExists = 0
    
    // Search the user database to find required information about the user
    var response = ""
    userDatabaseParser.forEachLine { line ->
        if (line.contains(username)) {
            response = "Username already exists"
        }
        lineNumber++
    }
    if (response != "") {
        return(response)
    }
    userDatabaseParser.close()
    if (username == "") {
        return("No username")
    }

    if (token == "") {
        return("No token")
    }

    val userDatabaseFile = File("userDatabase")
    userDatabaseFile.appendText("${System.lineSeparator()}$username:$token")
    return("Success")
}
fun authKey(inputData: String): String {
    println("API request recieved: $inputData")
       
    // Parse data sent to the server by client
    var username = ""
    var token = ""
    var authKey = ""
    var dataType = ""
    var isParsingData = 0
    for (char in inputData) {
        val character = char
        if (character == ':') {
            isParsingData = 1
        } else if (isParsingData == 1) {
            if (character == '}') {
                isParsingData = 0
                dataType = ""
            } else if (character != '{') {
                if (dataType == "username") {
                    username += character
                } else if (dataType == "token") {
                    token += character
                } else if (dataType == "authkey") {
                    authKey += character
                }
            }
        } else {
            dataType += character
        }
    }
    val userDatabaseParser = BufferedReader(File("userDatabase").reader())
    var lineNumber = 1
    var userLine = ""
    
    // Search the user database to find required information about the user
    userDatabaseParser.forEachLine { line ->
        if (line.contains(username)) {
            userLine = line
        }
        lineNumber++
    }
    userDatabaseParser.close()

    if (userLine == "") {
        return("Account not found")
    }
 
    var usernameInDatabase = ""
    var tokenInDatabase = ""
    var currentStage = 0
    for (char in userLine) {
        if (char == ':') {
            currentStage ++
        }
        if (currentStage == 0) {
            usernameInDatabase += char
        } else if (currentStage == 1) {
            tokenInDatabase += char
        }
    }
    tokenInDatabase = tokenInDatabase.replace(":", "")
    if (token != tokenInDatabase)  {
        return("Invalid token")
    }
    if (authKey == "") {
        return("No auth key provided")
    }
    // Make the message to respond to the client
    val chatHistoryView = File("chatHistory")
    var fullMessage = ""
    if (authKey != "") {
        fullMessage = "encryptionKey:$username:$authKey"
        authKey = ""
    } else {
        fullMessage = "${chatHistoryView.readText()}"
    }
    val response = if (inputData.isNotEmpty()) {
        fullMessage            
    } else {
        "No data provided"
    }

    // Send the message to the client
    return("Success")
}

fun main(args: Array<String>) {
    WsSessionManager.peopleOnline.removeAt(0)
    WsSessionManager.sessionsList.removeAt(0)
    val app = Javalin.create()
        .get("/") { ctx -> ctx.result("dingus") }
        .get("/api/send/{content}") { ctx -> 
            val result = handleSentMessage(ctx.pathParam("content"))
            if (result == "Success") {
                val messageContent = extractMessageContent(ctx.pathParam("content"))
                WsSessionManager.broadcast(messageContent)
            }
            ctx.result(result)
        }
        .get("/api/createaccount/{content}") { ctx -> ctx.result(createAccount(ctx.pathParam("content")))}
        .get("/api/syncmessages/{content}") { ctx -> ctx.result(syncMessages(ctx.pathParam("content")))}
        .get("/api/authkey/{content}") { ctx -> ctx.result(authKey(ctx.pathParam("content")))}
        
        .ws("/api/websocket") { ws -> 
            ws.onConnect { ctx -> 
                WsSessionManager.addSession(ctx)
            }
            ws.onClose { ctx ->
                WsSessionManager.removeSession(ctx)
            }
            ws.onMessage { ctx ->
                when (ctx.message()) {
                    "pong" -> {}
                else -> {
                    println(ctx.message()) 
                    val successState = handleSentMessage(ctx.message())
                    if (successState != "Success") {
                        try {
                            ctx.send(successState)
                        } catch (e: Exception) {
                            println("Error sending error message: ${e.message}")
                        }
                    } else {
                        val messageContent = extractMessageContent(ctx.message())
                        WsSessionManager.broadcast(messageContent)
                    }
                }
            }
            }
        }
    .start(7070)

}