package xyz.maxwellj.chookchat

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

import kotlin.concurrent.fixedRateTimer

import org.json.JSONObject
import org.json.JSONArray
import org.json.JSONException

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

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

import java.nio.file.Paths
import java.nio.file.Files

fun md5(input:String): String {
    val md = MessageDigest.getInstance("MD5")
    return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
}
/*
fun removeLines(fileName: String, lineNumber: String) {
    require(!fileName.isEmpty() && startLine >= 1 && numLines >= 1)
    val f = File(fileName)
    var lines = f.readLines()
    if (startLine > size) {
        println("The starting line is beyond the length of the file")
        return
    }
    lines = lines.take(startLine - 1) + lines.drop(startLine + n - 1)
    val text = lines.joinToString(System.lineSeparator())
    f.writeText(text)
}
*/
object WsSessionManager {
    val peopleOnline = mutableListOf("")
    val sessionsList = mutableListOf("")
    val sessions = ConcurrentHashMap<WsContext, String>()
    val sessionIds = ConcurrentHashMap<String, WsContext>()
    val userSessions = ConcurrentHashMap<String, String>()

    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() {
        val processedData = JSONObject().apply {
            put("type", "users")
            put("username", "system")
            put("content", peopleOnline.joinToString(", "))
        }
        broadcast(processedData.toString())
    }

    fun handleUserLogin(username: String) {
        if (!peopleOnline.contains(username)) {
            peopleOnline.add(username)
            broadcastOnlineUsers()
        }
    }

    fun addSession(ctx: WsContext) {
        try {
            val sessionId = UUID.randomUUID().toString()
            sessionsList.add(sessionId)  // Changed from += to add()
            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) {
                // Find and remove the username associated with this session
                userSessions.entries.find { it.value == sessionId }?.let { entry ->
                    peopleOnline.remove(entry.key)
                    userSessions.remove(entry.key)
                }

                sessionsList.remove(sessionId)
                sessions.remove(ctx)
                sessionIds.remove(sessionId)
                broadcastOnlineUsers()
            }
        } catch (e: Exception) {
            println("Error removing session: ${e.message}")
        }
    }

    fun associateUserWithSession(username: String, ctx: WsContext) {
        val sessionId = sessions[ctx]
        if (sessionId != null) {
            userSessions[username] = sessionId
        }
    }

    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, ctx: WsContext): String {
    val jsonInputData = JSONObject(inputData)
    if (jsonInputData.getString("type") == "connect") {
        val username = jsonInputData.getString("username")
        WsSessionManager.associateUserWithSession(username, ctx)
        WsSessionManager.handleUserLogin(username)
        val processedData = JSONObject().apply {
            put("type", "connect")
            put("username", "system")
            put("content", "${jsonInputData.getString("username")} just joined the room!")
        }
        return(processedData.toString())
    }
    val processedData = JSONObject().apply {
        put("type", jsonInputData.getString("type"))
        put("username", jsonInputData.getString("username"))
        put("content", jsonInputData.getString("content"))
    }    
    return(processedData.toString())
}

fun handleSentMessage(inputData: String): String {
    println("API request recieved: $inputData")
    var jsonInputData: JSONObject
    try {jsonInputData = JSONObject(inputData)} catch (error: JSONException){return(error.toString())}
    
    val username = jsonInputData.getString("username")
    val token = jsonInputData.getString("token")
    val content = jsonInputData.getString("content")

    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 == "") {
        val processedData = JSONObject().apply {
            put("type", "error")
            put("username", "system")
            put("content", "unknown-account")
        }    
        return(processedData.toString())
    }
 
    var usernameInDatabase = ""
    var tokenInDatabase = ""
    var saltInDatabase = ""
    var banStatus = ""
    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
        } else if (currentStage == 3) {
            banStatus += char
        }
    }
    tokenInDatabase = tokenInDatabase.replace(":", "")
    saltInDatabase = saltInDatabase.replace(":", "")
    banStatus = banStatus.replace(":", "")
    if (banStatus == "1") {
        val processedData = JSONObject().apply {
            put("type", "error")
            put("username", "system")
            put("content", "banned")
        }    
        return(processedData.toString())
    }
    val tokenWithSalt = (md5(token + saltInDatabase))
    /*println(saltInDatabase)
    println(tokenWithSalt)
    if (tokenWithSalt != tokenInDatabase)  {*/
    if (token != tokenInDatabase) {
        val processedData = JSONObject().apply {
            put("type", "error")
            put("username", "system")
            put("content", "invalid-token")
        }
        return(processedData.toString())
    }
    // Make the message to respond to the client
    val chatHistoryView = File("chatHistory")
    var fullMessage = ""
    if (content != "") {
        fullMessage = "${chatHistoryView.readText()}$username: $content"
        // Add the client's message to the chat history
        val chatHistory = File("chatHistory")
        chatHistory.appendText("$username: $content ${System.lineSeparator()}")
        return("Success")
    } else {
        return("No data provided")
    }
    return("Chookchat")
}

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)) {
            val processedData = JSONObject().apply {
                put("type", "error")
                put("username", "system")
                put("content", "username-taken")
            }
            response = processedData.toString()       
        }
        lineNumber++
    }
    if (response != "") {
        return(response)
    }
    userDatabaseParser.close()
    if (username == "") {
        val processedData = JSONObject().apply {
            put("type", "error")
            put("username", "system")
            put("content", "no-username")
        }
        return(processedData.toString())
    }

    if (token == "") {
        val processedData = JSONObject().apply {
            put("type", "error")
            put("username", "system")
            put("content", "no-token")
        }
        return(processedData.toString())
    }

    val userDatabaseFile = File("userDatabase")
    userDatabaseFile.appendText("${System.lineSeparator()}$username:$token")
    val processedData = JSONObject().apply {
        put("type", "success")
        put("username", "system")
        put("content", "success")
    }
    return(processedData.toString())
}

fun handleServerCommand(command: String): String {
    val commandArgs = mutableListOf("")
    commandArgs.drop(1)
    var currentStage = 0

    for (char in command) {
        if (char == ' ') {
            currentStage ++
            commandArgs += ""
        } else {
            commandArgs[currentStage] += char
        }
    }
    return("I'm not sure how to ${commandArgs.toString()}")
}

fun main(args: Array<String>) {
    WsSessionManager.peopleOnline.removeAt(0)
    WsSessionManager.sessionsList.removeAt(0)
    val app = Javalin.create { config ->
            config.staticFiles.add("/public")
        }.get("/") { ctx -> 
            ctx.redirect("/index.html") 
        }
        .get("/api/createaccount/{content}") { ctx -> ctx.result(createAccount(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(), ctx)
                        WsSessionManager.broadcast(messageContent)
                    }
                }
            }
            }
        }
    .start(7070)
    try {
        if (args[0] == "-i") {
            println("Type a command for the server")
            while (1 == 1) {
                println(handleServerCommand(readln()))
            }
        } else {
            println("Interactive mode disabled, add -i to enable")
        }
    } catch (error: Exception) {
        println("Interactive mode disabled, add -i to enable")
    }
}