/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2026 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.sql

import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.*
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.dao.CompositeEntity
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
import org.jetbrains.exposed.v1.javatime.timestamp
import org.jetbrains.exposed.v1.jdbc.select
import org.jetbrains.exposed.v1.jdbc.upsert

object LeaderboardTable : CompositeIdTable("Leaderboard") {
    val userId = reference("UserId", SteamwarUserTable)
    val name = varchar("LeaderboardName", 64).entityId()
    val time = long("Time")
    val updatedAt = timestamp("UpdatedAt").defaultExpression(CurrentTimestamp)
    val bestTime = bool("BestTime")

    override val primaryKey = PrimaryKey(userId, name)

    init {
        addIdColumn(userId)
    }
}

class Leaderboard(id: EntityID<CompositeID>) : CompositeEntity(id) {
    companion object : CompositeEntityClass<Leaderboard>(LeaderboardTable) {
        @JvmStatic
        fun getLeaderboard(name: String) =
            useDb {
                find { LeaderboardTable.name eq name }.orderBy(LeaderboardTable.time to SortOrder.ASC).limit(5).toList()
            }

        @JvmStatic
        fun getPlayerTime(user: SteamwarUser, name: String) =
            useDb {
                find { (LeaderboardTable.userId eq user.id.value) and (LeaderboardTable.name eq name) }.firstOrNull()
            }

        @JvmStatic
        fun getPlayerPlacement(user: SteamwarUser, name: String) =
            useDb {
                LeaderboardTable.select(LeaderboardTable.time.count())
                    .where {
                        (LeaderboardTable.name eq name) and (LeaderboardTable.time lessSubQuery LeaderboardTable.select(
                            LeaderboardTable.time
                        ).where { (LeaderboardTable.userId eq user.id.value) and (LeaderboardTable.name eq name) })
                    }
                    .firstOrNull()?.get(LeaderboardTable.time.count())?.toInt()
                    ?: Int.MAX_VALUE
            }

        @JvmStatic
        fun upsert(userId: Int, name: String, time: Long, bestTime: Boolean) =
            useDb {
                LeaderboardTable.upsert(
                    onUpdateExclude = listOf(LeaderboardTable.updatedAt, LeaderboardTable.userId, LeaderboardTable.name)
                ) {
                    it[LeaderboardTable.userId] = userId
                    it[LeaderboardTable.name] = name
                    it[LeaderboardTable.time] = time
                    it[LeaderboardTable.bestTime] = bestTime
                }
            }
    }

    val user by LeaderboardTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
    val name by LeaderboardTable.name
    var time by LeaderboardTable.time
    var updatedAt by LeaderboardTable.updatedAt
    var bestTime by LeaderboardTable.bestTime
}