/*
 *   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.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 java.util.*

object EventGroupTable : IntIdTable("EventGroup", "Id") {
    val event = reference("EventID", EventTable)
    val name = varchar("Name", 64)
    val type = enumeration("Type", EventGroup.EventGroupType::class)
    val pointsPerWin = integer("PointsPerWin").default(3)
    val pointsPerLoss = integer("PointsPerLoss").default(0)
    val pointsPerDraw = integer("PointsPerDraw").default(1)

    init {
        uniqueIndex(event, name)
    }
}

class EventGroup(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<EventGroup>(EventGroupTable) {

        @JvmStatic
        fun get(event: Event) =
            useDb { find { EventGroupTable.event eq event.id }.toList() }

        @JvmStatic
        fun byId(groupId: Int) =
            useDb { Optional.ofNullable(findById(groupId)) }

        @JvmStatic
        fun create(event: Event, name: String, type: EventGroupType) =
            useDb {
                new {
                    this.eventID = event.id.value
                    this.groupName = name
                    this.groupType = type
                }
            }
    }

    var eventID by EventGroupTable.event.transform({ EntityID(it, EventTable) }, { it.value })
        private set
    private var groupName by EventGroupTable.name
    private var groupType by EventGroupTable.type
    private var groupPointsPerWin by EventGroupTable.pointsPerWin
    private var groupPointsPerLoss by EventGroupTable.pointsPerLoss
    private var groupPointsPerDraw by EventGroupTable.pointsPerDraw
    val fights by lazy { useDb { EventFight.find { EventFightTable.groupId eq id }.toList() } }
    val teamsId by lazy { fights.flatMap { listOf(it.teamBlue, it.teamRed) }.toSet() }

    var name: String
        get() = groupName
        set(value) {
            groupName = value
        }
    var type: EventGroupType
        get() = groupType
        set(value) {
            groupType = value
        }
    var pointsPerWin: Int
        get() = groupPointsPerWin
        set(value) {
            groupPointsPerWin = value
        }
    var pointsPerLoss: Int
        get() = groupPointsPerLoss
        set(value) {
            groupPointsPerLoss = value
        }
    var pointsPerDraw: Int
        get() = groupPointsPerDraw
        set(value) {
            groupPointsPerDraw = value
        }
    val dependents by lazy { EventRelation.getGroupRelations(this) }
    val lastFight by lazy { Optional.ofNullable(fights.maxByOrNull { it.startTime }) }

    fun getId() =
        id.value

    private var points: Map<Team, Int>? = null

    fun calculatePoints(): Map<Team, Int> {
        if (points == null) {
            val p: MutableMap<Int, Int> = teamsId.associateWith { 0 }.toMutableMap()

            for (fight in fights) {
                var blueTeamAdd = 0
                var redTeamAdd = 0

                if (!fight.hasFinished()) {
                    continue
                }

                when (fight.ergebnis) {
                    1 -> {
                        blueTeamAdd += pointsPerWin
                        redTeamAdd += pointsPerLoss
                    }

                    2 -> {
                        blueTeamAdd += pointsPerLoss
                        redTeamAdd += pointsPerWin
                    }

                    0 -> if (fight.fight != null) {
                        blueTeamAdd += pointsPerDraw
                        redTeamAdd += pointsPerDraw
                    }
                }

                p[fight.teamBlue] = p[fight.teamBlue]?.plus(blueTeamAdd)
                    ?: blueTeamAdd
                p[fight.teamRed] = p[fight.teamRed]?.plus(redTeamAdd)
                    ?: redTeamAdd
            }

            return p.mapKeys { Team.byId(it.key) }.also { points = it }
        } else {
            return points!!
        }
    }

    fun needsTieBreak() =
        calculatePoints().values.let { it.size == it.toSet().size }

    fun update(name: String, type: EventGroupType, pointsPerWin: Int, pointsPerLoss: Int, pointsPerDraw: Int) =
        useDb {
            this@EventGroup.name = name
            this@EventGroup.type = type
            this@EventGroup.pointsPerWin = pointsPerWin
            this@EventGroup.pointsPerLoss = pointsPerLoss
            this@EventGroup.pointsPerDraw = pointsPerDraw
        }

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

    enum class EventGroupType {
        GROUP_STAGE,
        ELIMINATION_STAGE
    }
}