package de.kif.backend.database

import de.kif.backend.Configuration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mu.KotlinLogging
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
import java.nio.file.Files
import java.sql.Connection.TRANSACTION_SERIALIZABLE
import kotlin.system.exitProcess


object Connection {

    private val logger = KotlinLogging.logger {}

    private val schemaList = arrayOf(
        DbTrack, DbWorkGroup,
        DbRoom, DbSchedule,
        DbUser, DbUserPermission,
        DbPost
    )

    fun init() {
        val dbPath = Configuration.Path.databasePath.toString()
        Database.connect("jdbc:sqlite:$dbPath", "org.sqlite.JDBC")
        TransactionManager.manager.defaultIsolationLevel = TRANSACTION_SERIALIZABLE

        try {
            create()
        } catch (e: ExposedSQLException) {
            logger.error { "Cannot initialize the database!" }

            print("Do you want to recreate the database (you will lose all data)? [y/N]: ")
            val result = readLine()

            if (result == null || result.toLowerCase() !in "yes") {
                exitProcess(1)
            } else {
                reset()
            }
        }
    }

    private fun create() {
        transaction {
            SchemaUtils.createMissingTablesAndColumns(*schemaList)
        }
    }

    private fun delete() {
        transaction {
            SchemaUtils.drop(*schemaList)
        }
    }

    fun reset() {
        delete()

        Configuration.Path.uploadsPath.toFile().deleteRecursively()
        Configuration.Path.sessionsPath.toFile().deleteRecursively()

        Files.createDirectory(Configuration.Path.uploadsPath)
        Files.createDirectory(Configuration.Path.sessionsPath)

        create()
    }
}

suspend fun <T> dbQuery(block: () -> T): T = withContext(Dispatchers.IO) {
    transaction { block() }
}
