/*
 *   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.IntegerColumnType
import org.jetbrains.exposed.v1.core.ReferenceOption
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.eq
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.insertAndGetId
import org.jetbrains.exposed.v1.jdbc.update
import java.sql.Timestamp

object FightTable : IntIdTable("Fight", "FightId") {
    val gamemode = varchar("Gamemode", 30).index()
    val server = text("Server")
    val startTime = timestamp("StartTime")
    val duration = integer("Duration")
    val blueLeader = reference("BlueLeader", SteamwarUserTable).index()
    val redLeader = reference("RedLeader", SteamwarUserTable).index()
    val blueSchem = optReference("BlueSchem", SchematicNodeTable, onDelete = ReferenceOption.SET_NULL).index()
    val redSchem = optReference("RedSchem", SchematicNodeTable, onDelete = ReferenceOption.SET_NULL).index()
    val win = enumeration("Win", Fight.WinningTeam::class)
    val winCondition = varchar("WinCondition", 100)
    val replayAvailable = bool("ReplayAvailable")
}

class Fight(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<Fight>(FightTable) {
        @JvmStatic
        fun getById(id: Int) =
            useDb { get(id) }

        @JvmStatic
        fun create(
            gamemode: String,
            server: String,
            starttime: Timestamp,
            duration: Int,
            blueleader: Int,
            redleader: Int,
            blueschem: Int?,
            redschem: Int?,
            win: Int,
            wincondition: String
        ): Int =
            useDb {
                FightTable.insertAndGetId {
                    it[FightTable.gamemode] = gamemode
                    it[FightTable.server] = server
                    it[FightTable.startTime] = starttime.toInstant()
                    it[FightTable.duration] = duration
                    it[FightTable.blueLeader] = EntityID(blueleader, SteamwarUserTable)
                    it[FightTable.redLeader] = EntityID(redleader, SteamwarUserTable)
                    it[FightTable.blueSchem] = blueschem?.let { EntityID(it, SchematicNodeTable) }
                    it[FightTable.redSchem] = redschem?.let { EntityID(it, SchematicNodeTable) }
                    it[FightTable.win] = WinningTeam.entries[win]
                    it[FightTable.winCondition] = wincondition
                }.value
            }

        @JvmStatic
        fun getPage(page: Int, pageSize: Int): List<Fight> =
            useDb {
                val fights = all().orderBy(FightTable.startTime to SortOrder.DESC).limit(pageSize).offset((pageSize * page).toLong())

                val fightPlayer = FightPlayer.batchGet(fights.map { it.id.value })
                for (fight in fights) {
                    fight.initPlayers(fightPlayer)
                }

                SteamwarUser.batchCache(fightPlayer.map { it.userID }.toMutableSet())
                fights.toList()
            }

        @JvmStatic
        fun markReplayAvailable(id: Int) =
            useDb {
                FightTable.update({ FightTable.id eq id }) {
                    it[replayAvailable] = true
                }
            }
    }

    val fightID by FightTable.id.transform({ EntityID(it, FightTable) }, { it.value })
    val gameMode by FightTable.gamemode
    val server by FightTable.server
    val startTime by FightTable.startTime.transform({ it.toInstant() }, { Timestamp.from(it) })
    val duration by FightTable.duration
    val blueLeaderId by FightTable.blueLeader
    val blueLeader by lazy { useDb { SteamwarUser[blueLeaderId] } }
    val redLeaderId by FightTable.redLeader
    val redLeader by lazy { useDb { SteamwarUser[redLeaderId] } }
    val blueSchem by FightTable.blueSchem
    val redSchem by FightTable.redSchem
    val winner by FightTable.win
    val win: Int
        get() = winner.ordinal
    val winCondition by FightTable.winCondition
    val replayAvailable by FightTable.replayAvailable

    val schemType: SchematicType?
        get() = SchematicType.fromDB(gameMode)

    val replayAllowed by lazy {
        replayExists() && useDb {
            exec(
                "SELECT (b.NodeId IS NULL OR b.Config & 2) AND (r.NodeId IS NULL OR r.Config & 2) AS ReplayAllowed FROM Fight f LEFT OUTER JOIN SchematicNode b ON f.BlueSchem = b.NodeId LEFT OUTER JOIN SchematicNode r ON f.RedSchem = r.NodeId WHERE FightId = ?",
                args = listOf(IntegerColumnType() to this@Fight.id.value)
            ) {
                if (it.next()) {
                    it.getBoolean("ReplayAllowed")
                } else {
                    false
                }
            }
                ?: false
        }
    }

    fun replayExists() =
        schemType != null && replayAvailable

    fun replayAllowed() =
        replayAllowed

    lateinit var bluePlayers: List<FightPlayer>
    lateinit var redPlayers: List<FightPlayer>

    private fun initPlayers(fightPlayers: List<FightPlayer>) {
        val blue = mutableListOf<FightPlayer>()
        val red = mutableListOf<FightPlayer>()

        for (player in fightPlayers.filter { it.fightID == id.value }) {
            if (player.team == 1) {
                blue.add(player)
            } else {
                red.add(player)
            }
        }

        bluePlayers = blue
        redPlayers = red
    }

    enum class WinningTeam {
        NONE, BLUE, RED;
    }
}