package de.steamwar.db

import com.github.ajalt.clikt.core.BaseCliktCommand
import com.github.ajalt.clikt.core.CliktError
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarUserTable
import org.jetbrains.exposed.v1.core.Expression
import org.jetbrains.exposed.v1.core.Op
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.JdbcTransaction
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.io.File
import java.sql.ResultSet
import java.util.*

object Database {
    val host: String
    val port: String
    val database: String
    val username: String
    val password: String
    lateinit var db: Database

    init {
        val config = File(System.getProperty("user.home"), "mysql.properties")

        if (!config.exists()) {
            throw CliktError("Config file not found!")
        }

        val props = Properties();

        props.load(config.inputStream())

        host = props.getProperty("host")
        port = props.getProperty("port")
        database = props.getProperty("database")
        username = props.getProperty("user")
        password = props.getProperty("password")
    }

    fun ensureConnected() {
        if (::db.isInitialized) {
            return
        }

        val url = "jdbc:mariadb://$host:$port/$database"

        db = Database.connect(url, driver = "org.mariadb.jdbc.Driver", user = username, password = password)
        return
    }
}

fun <T : BaseCliktCommand<T>> BaseCliktCommand<T>.findUser(query: String): SteamwarUser? =
    transaction {
        SteamwarUser.find { joinedOr(query.toIntOrNull()?.let { SteamwarUserTable.id eq it }, (SteamwarUserTable.username eq query), SteamwarUserTable.uuid eq query, query.toLongOrNull()?.let { SteamwarUserTable.discordId eq it }) }
            .firstOrNull()
            ?.let { return@transaction it }
    }

fun joinedOr(vararg expressions: Expression<Boolean>?): Op<Boolean> =
    expressions.filterNotNull().reduce { acc, expression -> acc or expression } as Op<Boolean>


fun <T> JdbcTransaction.execute(sql: String, transform: (ResultSet) -> T): List<T> {
    val result = mutableListOf<T>()
    exec(sql) { rs ->
        while (rs.next()) {
            result += transform(rs)
        }
    }
    return result
}

fun <T> JdbcTransaction.executeSingle(sql: String, transform: (ResultSet) -> T): T? {
    return execute(sql) { rs ->
        if (!rs.next()) {
            return@execute null
        }
        transform(rs)
    }.single()
}

fun JdbcTransaction.executeScript(sql: String) {
    for (statement in splitSqlScript(sql)) {
        exec(statement) { }
    }
}

private fun splitSqlScript(sql: String): List<String> {
    val statements = mutableListOf<String>()
    val current = StringBuilder()
    var quote: Char? = null
    var inLineComment = false
    var inBlockComment = false
    var index = 0

    fun addStatement() {
        val statement = current.toString().trim()
        if (statement.isNotEmpty()) {
            statements += statement
        }
        current.clear()
    }

    while (index < sql.length) {
        val char = sql[index]
        val next = sql.getOrNull(index + 1)

        if (inLineComment) {
            current.append(char)
            if (char == '\n') {
                inLineComment = false
            }
            index++
            continue
        }

        if (inBlockComment) {
            current.append(char)
            if (char == '*' && next == '/') {
                current.append(next)
                inBlockComment = false
                index += 2
            } else {
                index++
            }
            continue
        }

        if (quote != null) {
            current.append(char)
            if (char == '\\' && quote != '`' && next != null) {
                current.append(next)
                index += 2
                continue
            }
            if (char == quote) {
                if (next == quote) {
                    current.append(next)
                    index += 2
                    continue
                }
                quote = null
            }
            index++
            continue
        }

        when {
            char == '-' && next == '-' -> {
                current.append(char).append(next)
                inLineComment = true
                index += 2
            }

            char == '#' -> {
                current.append(char)
                inLineComment = true
                index++
            }

            char == '/' && next == '*' -> {
                current.append(char).append(next)
                inBlockComment = true
                index += 2
            }

            char == '\'' || char == '"' || char == '`' -> {
                current.append(char)
                quote = char
                index++
            }

            char == ';' -> {
                addStatement()
                index++
            }

            else -> {
                current.append(char)
                index++
            }
        }
    }

    addStatement()
    return statements
}

fun useDb(statement: JdbcTransaction.() -> Unit) {
    de.steamwar.db.Database.ensureConnected()
    transaction(de.steamwar.db.Database.db, statement = statement)
}
