package de.krisenvorrat.server.db

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.update
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.mindrot.jbcrypt.BCrypt
import org.slf4j.LoggerFactory
import java.util.UUID

private val logger = LoggerFactory.getLogger("DatabaseFactory")

internal object DatabaseFactory {

    fun init(
        jdbcUrl: String = System.getenv("KRISENVORRAT_DB_URL")
            ?: "jdbc:postgresql://localhost:5432/krisenvorrat",
        driver: String = System.getenv("KRISENVORRAT_DB_DRIVER")
            ?: "org.postgresql.Driver",
        dbUser: String = System.getenv("KRISENVORRAT_DB_USER") ?: "krisenvorrat",
        dbPassword: String = System.getenv("KRISENVORRAT_DB_PASSWORD") ?: "krisenvorrat",
        adminPassword: String? = System.getenv("KRISENVORRAT_ADMIN_PASSWORD"),
        usePool: Boolean = !driver.contains("h2", ignoreCase = true)
    ) {
        if (usePool) {
            val config = HikariConfig().apply {
                this.jdbcUrl = jdbcUrl
                this.driverClassName = driver
                this.username = dbUser
                this.password = dbPassword
                maximumPoolSize = 10
                isAutoCommit = false
                transactionIsolation = "TRANSACTION_REPEATABLE_READ"
                validate()
            }
            Database.connect(HikariDataSource(config))
        } else {
            Database.connect(jdbcUrl, driver)
        }
        transaction {
            SchemaUtils.create(Inventories, Users, Categories, Locations, Items, Settings, Messages, DeletedItems)
            SchemaUtils.createMissingTablesAndColumns(Inventories, Users, Categories, Locations, Items, Settings, Messages, DeletedItems)
        }
        migrateUserInventories()
        seedAdmin(adminPassword)
    }

    /**
     * Migration: For each existing user without an inventoryId, create an inventory
     * and migrate their data from the old user_id column to the new inventory_id column.
     */
    private fun migrateUserInventories() {
        transaction {
            val usersWithoutInventory = Users.selectAll()
                .where { Users.inventoryId.isNull() }
                .map { it[Users.id] }

            for (userId in usersWithoutInventory) {
                val inventoryId = UUID.randomUUID().toString()
                Inventories.insert {
                    it[id] = inventoryId
                    it[createdAt] = System.currentTimeMillis()
                }
                Users.update({ Users.id eq userId }) {
                        it[Users.inventoryId] = inventoryId
                    }
                // Migrate existing data from old user_id column to new inventory_id column.
                // The exec() calls are wrapped in try/catch because the user_id column may not
                // exist in fresh databases (only in upgraded databases from older schema).
                try {
                    exec("UPDATE items SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
                    exec("UPDATE categories SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
                    exec("UPDATE locations SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
                    exec("UPDATE settings SET inventory_id = '$inventoryId' WHERE user_id = '$userId' AND inventory_id IS NULL")
                } catch (_: Exception) {
                    // user_id column doesn't exist – fresh database, nothing to migrate
                }
            }
        }
    }

    private fun seedAdmin(adminPassword: String?) {
        transaction {
            val adminExists = Users.selectAll().where { Users.username eq "admin" }.count() > 0
            if (!adminExists) {
                val password = if (!adminPassword.isNullOrBlank()) {
                    adminPassword
                } else {
                    val generated = UUID.randomUUID().toString().replace("-", "").take(16)
                    logger.warn("==============================================")
                    logger.warn("KRISENVORRAT_ADMIN_PASSWORD not set.")
                    logger.warn("Generated admin password: $generated")
                    logger.warn("==============================================")
                    generated
                }
                val adminId = UUID.randomUUID().toString()
                val inventoryId = UUID.randomUUID().toString()
                Inventories.insert {
                    it[id] = inventoryId
                    it[createdAt] = System.currentTimeMillis()
                }
                Users.insert {
                    it[id] = adminId
                    it[username] = "admin"
                    it[passwordHash] = BCrypt.hashpw(password, BCrypt.gensalt())
                    it[createdAt] = System.currentTimeMillis()
                    it[isAdmin] = true
                    it[Users.inventoryId] = inventoryId
                }
            }
        }
    }
}
