/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 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.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.javatime.timestamp
import org.jetbrains.exposed.v1.jdbc.select
import java.sql.Timestamp
import java.time.Instant
import java.util.*
import java.util.function.Consumer

object PunishmentTable : IntIdTable("Punishments", "PunishmentId") {
    val userId = reference("UserId", SteamwarUserTable)
    val punisher = reference("Punisher", SteamwarUserTable).index()
    val type = enumerationByName("Type", 32, Punishment.PunishmentType::class)
    val startTime = timestamp("StartTime")
    val endTime = timestamp("EndTime")
    val perma = bool("Perma")
    val reason = text("Reason")

    init {
        index(false, userId, type)
    }
}

class Punishment(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<Punishment>(PunishmentTable) {
        @JvmField
        val PERMA_TIME: Timestamp = Timestamp.from(Instant.ofEpochSecond(946674800))

        @JvmStatic
        fun getPunsihmentOfPlayer(user: Int, type: PunishmentType) =
            useDb {
                find { (PunishmentTable.userId eq user) and (PunishmentTable.type eq type) }.orderBy(PunishmentTable.id to SortOrder.DESC)
                    .firstOrNull()
            }

        @JvmStatic
        fun getPunishmentsOfPlayer(user: Int) =
            useDb {
                find {
                    PunishmentTable.id inSubQuery PunishmentTable.select(PunishmentTable.id.max())
                        .where { PunishmentTable.userId eq user }.groupBy(
                            PunishmentTable.type
                        )
                }.associateBy { it.type }.toMutableMap()
            }

        @JvmStatic
        fun getAllPunishmentsOfPlayer(user: Int) =
            useDb {
                find { PunishmentTable.userId eq user }.orderBy(PunishmentTable.id to SortOrder.DESC).toList()
            }

        @JvmStatic
        fun isPunished(user: SteamwarUser, type: PunishmentType, callback: Consumer<Punishment>): Boolean =
            useDb {
                val punishment = getPunsihmentOfPlayer(user.id.value, type)
                    ?: return@useDb false

                if (punishment.isCurrent()) {
                    callback.accept(punishment)
                    return@useDb true
                }

                return@useDb false
            }

        @JvmStatic
        fun createPunishment(
            user: Int,
            executor: Int,
            type: PunishmentType,
            reason: String,
            endTime: Timestamp,
            perma: Boolean
        ) =
            useDb {
                new {
                    this.userId = user
                    this.punisher = executor
                    this.type = type
                    this.startTime = Timestamp.from(Instant.now())
                    this.endTime = endTime
                    this.perma = perma
                    this.reason = reason
                }
            }
    }

    var userId by PunishmentTable.userId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
        private set
    var punisher by PunishmentTable.punisher.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
        private set
    var type by PunishmentTable.type
        private set
    var startTime by PunishmentTable.startTime.transform({ it.toInstant() }, { Timestamp.from(it) })
        private set
    var endTime by PunishmentTable.endTime.transform({ it.toInstant() }, { Timestamp.from(it) })
        private set
    var perma by PunishmentTable.perma
        private set
    var reason by PunishmentTable.reason
        private set

    fun isPerma() =
        perma

    fun isCurrent() =
        perma || endTime.after(Date())

    enum class PunishmentType(
        val teamMessage: String?,
        val playerMessagePerma: String?,
        val playerMessageUntil: String?,
        val usageNotPunished: String?,
        val unpunishmentMessage: String?,
        val userPerm: UserPerm,
        val multi: Boolean = false
    ) {
        Ban("BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN", UserPerm.PUNISHMENTS),
        Mute("MUTE_TEAM", "MUTE_PERMA", "MUTE_UNTIL", "UNMUTE_ERROR", "UNMUTE", UserPerm.PUNISHMENTS),
        NoSchemReceiving(
            "NOSCHEMRECEIVING_TEAM",
            "NOSCHEMRECEIVING_PERMA",
            "NOSCHEMRECEIVING_UNTIL",
            "UNNOSCHEMRECEIVING_ERROR",
            "UNNOSCHEMRECEIVING",
            UserPerm.MODERATION
        ),
        NoSchemSharing(
            "NOSCHEMSHARING_TEAM",
            "NOSCHEMSHARING_PERMA",
            "NOSCHEMSHARING_UNTIL",
            "UNNOSCHEMSHARING_ERROR",
            "UNNOSCHEMSHARING",
            UserPerm.MODERATION
        ),
        NoSchemSubmitting(
            "NOSCHEMSUBMITTING_TEAM",
            "NOSCHEMSUBMITTING_PERMA",
            "NOSCHEMSUBMITTING_UNTIL",
            "UNNOSCHEMSUBMITTING_ERROR",
            "UNNOSCHEMSUBMITTING",
            UserPerm.CHECK
        ),
        NoDevServer(
            "NODEVSERVER_TEAM",
            "NODEVSERVER_PERMA",
            "NODEVSERVER_UNTIL",
            "UNNODEVSERVER_ERROR",
            "UNNODEVSERVER",
            UserPerm.PREFIX_DEVELOPER
        ),
        NoFightServer(
            "NOFIGHTSERVER_TEAM",
            "NOFIGHTSERVER_PERMA",
            "NOFIGHTSERVER_UNTIL",
            "UNNOFIGHTSERVER_ERROR",
            "UNNOFIGHTSERVER",
            UserPerm.MODERATION
        ),
        NoTeamServer(
            "NOTEAMSERVER_TEAM",
            "NOTEAMSERVER_PERMA",
            "NOTEAMSERVER_UNTIL",
            "UNNOTEAMSERVER_ERROR",
            "UNNOTEAMSERVER",
            UserPerm.MODERATION
        ),
        Note("NOTE_TEAM", null, null, null, null, UserPerm.PUNISHMENTS, true);

        fun isMulti() =
            multi
    }
}