/*
 *   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.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.insertAndGetId
import org.jetbrains.exposed.v1.jdbc.select
import java.sql.Timestamp
import java.time.Instant
import java.util.*

object EventFightTable : IntIdTable("EventFight", "FightID") {
    val eventId = reference("EventID", EventTable).index()
    val startTime = timestamp("StartTime").index()
    val gamemode = text("Spielmodus")
    val map = text("Map")
    val groupId = optReference("GroupId", EventGroupTable).index()
    val teamBlue = reference("TeamBlue", TeamTable).index()
    val teamRed = reference("TeamRed", TeamTable).index()
    val spectatePort = integer("SpectatePort").nullable()
    val bestOf = integer("BestOf")
    val ergebnis = integer("Ergebnis")
    val fight = optReference("Fight", FightTable).index()
}

class EventFight(id: EntityID<Int>) : IntEntity(id), Comparable<EventFight> {
    companion object : IntEntityClass<EventFight>(EventFightTable) {
        val fights: Queue<EventFight> = PriorityQueue()
            @JvmStatic get

        @JvmStatic
        fun byId(fightId: Int) =
            useDb { findById(fightId) }

        @JvmStatic
        fun byId(group: EventGroup) =
            useDb {
                find { EventFightTable.groupId eq group.id }.orderBy(EventFightTable.startTime to SortOrder.DESC).toList()
            }

        @JvmStatic
        fun getLast(group: EventGroup) =
            useDb {
                Optional.ofNullable(
                    find { EventFightTable.groupId eq group.id }.orderBy(EventFightTable.startTime to SortOrder.DESC)
                        .firstOrNull()
                )
            }

        @JvmStatic
        fun loadAllComingFights() =
            useDb {
                fights.clear()
                fights.addAll(find { EventFightTable.startTime greaterEq Instant.now() }.orderBy(EventFightTable.startTime to SortOrder.ASC))
            }

        @JvmStatic
        fun getEvent(eventId: Int) =
            useDb {
                find { EventFightTable.eventId eq eventId }.orderBy(EventFightTable.startTime to SortOrder.ASC).toList()
            }

        private var activeFightsCache: List<EventFight>? = null

        @JvmStatic
        fun clearActiveFightsCache() {
            activeFightsCache = null
        }

        @JvmStatic
        fun getActiveFights(): List<EventFight> {
            if (activeFightsCache == null) {
                activeFightsCache = useDb {
                    find {
                        EventFightTable.fight.isNull() and (EventFightTable.startTime less Instant.now()) and (EventFightTable.eventId.inSubQuery(
                            EventTable.select(
                                EventTable.id
                            )
                                .where { (EventTable.start less Instant.now()) and (EventTable.end greater Instant.now()) }))
                    }.orderBy(EventFightTable.startTime to SortOrder.ASC).toList()
                }
            }

            return activeFightsCache!!
        }

        @JvmStatic
        fun create(
            event: Int,
            from: Timestamp,
            spielmodus: String,
            map: String,
            blueTeam: Int,
            redTeam: Int,
            spectatePort: Int?
        ) =
            useDb {
                get(
                    EventFightTable.insertAndGetId {
                        it[eventId] = EntityID(event, EventTable)
                        it[startTime] = from.toInstant()
                        it[gamemode] = spielmodus
                        it[EventFightTable.map] = map
                        it[teamBlue] = EntityID(blueTeam, TeamTable)
                        it[teamRed] = EntityID(redTeam, TeamTable)
                        it[EventFightTable.spectatePort] = spectatePort
                    }
                )
            }
    }

    val fightID by EventFightTable.id.transform({ EntityID(it, EventFightTable) }, { it.value })
    var teamBlue by EventFightTable.teamBlue.transform({ EntityID(it, TeamTable) }, { it.value })
    var teamRed by EventFightTable.teamRed.transform({ EntityID(it, TeamTable) }, { it.value })
    private var fightErgebnis by EventFightTable.ergebnis
    var ergebnis: Int
        get() = fightErgebnis
        set(value) = useDb {
            fightErgebnis = value
        }
    var eventID by EventFightTable.eventId.transform({ EntityID(it, EventTable) }, { it.value })
    var startTime by EventFightTable.startTime.transform({ it.toInstant() }, { Timestamp.from(it) })
    var spielmodus by EventFightTable.gamemode
    var map by EventFightTable.map
    var groupId by EventFightTable.groupId
    val group by lazy { useDb { Optional.ofNullable(groupId).map { EventGroup[it] } } }
    var spectatePort by EventFightTable.spectatePort
    private var fightStat by EventFightTable.fight.transform({ it?.let { EntityID(it, EventFightTable) } }, { it?.value })
    var fight: Int?
        get() = fightStat
        set(value) = useDb {
            fightStat = value
        }
    val dependents by lazy { useDb { EventRelation.getFightRelations(this@EventFight).toList() } }

    val winner: Team?
        get() = useDb { if (ergebnis == 1) Team[teamBlue] else if (ergebnis == 2) Team[teamRed] else null }
    val losser: Team?
        get() = useDb { if (ergebnis == 1) Team[teamRed] else if (ergebnis == 2) Team[teamBlue] else null }

    fun setGroup(group: Int?) =
        useDb { groupId = group?.let { EntityID(it, EventGroupTable) } }

    fun hasFinished() =
        fight != null || ergebnis != 0

    fun reschedule() =
        useDb {
            startTime = Timestamp.from(Instant.now().plusSeconds(30))
        }

    override fun hashCode() =
        fightID

    override fun equals(other: Any?) =
        other is EventFight && other.fightID == fightID

    override fun compareTo(other: EventFight): Int =
        startTime.compareTo(other.startTime)

    fun update(
        startTime: Timestamp,
        spielmodus: String,
        map: String,
        teamBlue: Int,
        teamRed: Int,
        spectatePort: Int?
    ) =
        useDb {
            this@EventFight.startTime = startTime
            this@EventFight.spielmodus = spielmodus
            this@EventFight.map = map
            this@EventFight.teamBlue = teamBlue
            this@EventFight.teamRed = teamRed
            this@EventFight.spectatePort = spectatePort
        }

    override fun delete() =
        useDb {
            super.delete()
        }
}