/*
 *   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.tntleague.game

import de.steamwar.kotlin.util.Area
import de.steamwar.network.NetworkSender
import de.steamwar.network.packets.common.FightInfoPacket
import de.steamwar.scoreboard.SWScoreboard
import de.steamwar.sql.SteamwarUser
import de.steamwar.tntleague.colorByTeam
import de.steamwar.tntleague.config.TNTLeagueConfig
import de.steamwar.tntleague.config.TNTLeagueWorldConfig
import de.steamwar.tntleague.config.world
import de.steamwar.tntleague.events.DummyListener
import de.steamwar.tntleague.events.IngameListener
import de.steamwar.tntleague.events.LobbyListener
import de.steamwar.tntleague.inventory.DealerInventory
import de.steamwar.tntleague.message
import de.steamwar.tntleague.plugin
import de.steamwar.tntleague.util.TNTLeagueScoreboard
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.Sound
import org.bukkit.entity.Item
import org.bukkit.entity.Player
import org.bukkit.entity.TNTPrimed
import org.bukkit.event.HandlerList
import org.bukkit.event.Listener
import org.bukkit.inventory.ItemStack
import org.bukkit.scheduler.BukkitTask
import java.sql.Timestamp
import java.time.Instant

object TNTLeagueGame {
    var state: GameState = GameState.LOBBY
        set(value) {
            if (field.listener != value.listener) {
                HandlerList.unregisterAll(field.listener)
                plugin.server.pluginManager.registerEvents(value.listener, plugin)
            }
            field = value
        }

    var gameTimeRemaining: Int = TNTLeagueConfig.config.gameTime

    val blueTeam = TNTLeagueTeam(TNTLeagueConfig.config.blueTeam)
    val redTeam = TNTLeagueTeam(TNTLeagueConfig.config.redTeam)

    private lateinit var start: Timestamp

    private var task: Int? = null
    private lateinit var spawnerTask: BukkitTask
    private lateinit var timerTask: BukkitTask

    private fun setup() {
        assert(state == GameState.STARTING) { "Game is already running" }

        state = GameState.RUNNING

        plugin.server.onlinePlayers.forEach { SWScoreboard.impl.createScoreboard(it, TNTLeagueScoreboard(it)) }

        blueTeam.start()
        redTeam.start()

        updateFightinfo()

        message.broadcast("GAME_STARTED")

        start = Timestamp.from(Instant.now())

        var spawnCount = 0
        spawnerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
            val coinsToSpawn = if (spawnCount % 28 == 0) {
                DealerInventory.huge_coins
            } else if (spawnCount % 7 == 0) {
                DealerInventory.big_coins
            } else {
                DealerInventory.coins
            }
            spawnCount++

            if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.blueTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
                spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, coinsToSpawn)
            }
            if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.redTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
                spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, coinsToSpawn)
            }
        }, 5, 10)

        timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
            gameTimeRemaining--
            if (gameTimeRemaining == 0) {
                if (blueTeam.damagedBlocks > redTeam.damagedBlocks) {
                    win(blueTeam, WinReason.TIMEOUT)
                } else if (redTeam.damagedBlocks > blueTeam.damagedBlocks) {
                    win(redTeam, WinReason.TIMEOUT)
                } else {
                    draw(WinReason.TIMEOUT)
                }
                return@bukkit
            }

            if (gameTimeRemaining % 300 == 0) {
                message.broadcast("TIME_REMAINING", (gameTimeRemaining / 60))
                plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1f) }
            }
        }, 20, 20)
    }

    private fun bukkit(f: () -> Unit): () -> Unit =
        f

    private fun end() {
        if (state != GameState.RUNNING) return
        state = GameState.END

        plugin.server.onlinePlayers.forEach {
            it.gameMode = GameMode.SPECTATOR
            SWScoreboard.impl.removeScoreboard(it)
            it.playSound(it.location, Sound.ENTITY_ENDER_DRAGON_DEATH, 1f, 1f)
        }

        message.broadcast("GAME_ENDED")

        spawnerTask.cancel()

        var shutdown = 10

        plugin.server.scheduler.runTaskTimer(plugin, bukkit {
            if (shutdown == 0) {
                plugin.server.shutdown()
            }

            message.broadcast("SHUTDOWN", shutdown)

            shutdown--
        }, 20, 20)

        updateFightinfo()
    }

    private fun spawnItems(loc: Location, item: ItemStack) =
        plugin.server.worlds.first().dropItem(loc, item)

    fun getTeam(player: Player) =
        if (player in blueTeam.members) blueTeam else if (player in redTeam.members) redTeam else null

    fun getFreeTeam(player: Player) =
        if (TNTLeagueConfig.isEvent()) getEventFreeTeam(player) else getNormalFreeTeam(player)

    private fun getNormalFreeTeam(player: Player) =
        if (blueTeam.leader == null && (TNTLeagueConfig.config.blueLeader == null || player.uniqueId == TNTLeagueConfig.config.blueLeader)) blueTeam
        else if (redTeam.leader == null && TNTLeagueConfig.config.redLeader == null || player.uniqueId == TNTLeagueConfig.config.redLeader) redTeam
        else null

    private fun getEventFreeTeam(player: Player) =
        SteamwarUser.get(player.uniqueId)!!.let { user ->
            if (user.team == TNTLeagueConfig.config.eventTeamBlue.teamId && blueTeam.members.size < TNTLeagueConfig.config.event.maximumTeamMembers) blueTeam
            else if (user.team == TNTLeagueConfig.config.eventTeamRed.teamId && redTeam.members.size < TNTLeagueConfig.config.event.maximumTeamMembers) redTeam
            else null
        }

    fun checkStart() {
        if (blueTeam.isReady && redTeam.isReady) {
            blueTeam.leader?.inventory?.clear()
            redTeam.leader?.inventory?.clear()
            state = GameState.STARTING

            var countdown = TNTLeagueConfig.config.startDelay
            message.broadcast("GAME_STARTING", countdown.toString())
            task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, {
                plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f) }
                if (--countdown == 0) {
                    plugin.server.onlinePlayers.forEach { it.level = 0 }
                    task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null }
                    setup()
                } else {
                    plugin.server.onlinePlayers.forEach { it.level = countdown }
                }
            }, 20, 20)

            if (task == -1) {
                error("Failed to start countdown task")
            }

            updateFightinfo()
        }
    }

    fun playerLeave(player: Player) {
        blueTeam.invites.remove(player)
        redTeam.invites.remove(player)
        getTeam(player)?.apply {
            remove(player)
        }
    }

    fun reset() {
        assert(state == GameState.LOBBY || state == GameState.STARTING) { "Game is not in lobby or starting state" }

        if (state == GameState.STARTING) {
            task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null }
            state = GameState.LOBBY
        }

        updateFightinfo()
    }

    fun win(tntLeagueTeam: TNTLeagueTeam, reason: WinReason) {
        if (state != GameState.RUNNING) return
        end()
        plugin.server.onlinePlayers.forEach { message.send("TEAM_WIN", it, tntLeagueTeam.name) }
        explode(tntLeagueTeam.opposite)

        if (TNTLeagueConfig.isEvent()) {
            TNTLeagueConfig.config.eventFight.ergebnis = tntLeagueTeam.ergebnisInt
        }
    }

    fun draw(reason: WinReason) {
        if (state != GameState.RUNNING) return
        end()
        message.broadcast("DRAW")

        if (TNTLeagueConfig.isEvent()) {
            TNTLeagueConfig.config.eventFight.ergebnis = 3
        }
    }

    fun explode(team: TNTLeagueTeam) {
        Area(team.config.worldConfig.spawnLocation.clone().add(20.0, 30.0, 20.0), team.config.worldConfig.spawnLocation.clone().subtract(20.0, 0.0, 20.0).add(0.0, 30.0, 0.0))
            .locations
            .filterIndexed { index, _ -> index % 7 == 0 }
            .forEachIndexed { index, location ->
                world.spawn(location, TNTPrimed::class.java).apply {
                    fuseTicks = index + 40
                }
            }
    }

    fun updateFightinfo() {
        NetworkSender.send(
            FightInfoPacket(
                plugin.server.worlds.first().name,
                "TNTLeague",
                "",
                blueTeam.name.message.colorByTeam(blueTeam),
                redTeam.name.message.colorByTeam(redTeam),
                state.lobbyName,
                TNTLeagueConfig.config.gameTime - gameTimeRemaining,
                blueTeam.leader?.let { SteamwarUser.get(it.uniqueId)!!.getId() }
                    ?: 0,
                redTeam.leader?.let { SteamwarUser.get(it.uniqueId)!!.getId() }
                    ?: 0,
                0,
                0,
                blueTeam.members.map { SteamwarUser.get(it.uniqueId)!!.getId() },
                redTeam.members.map { SteamwarUser.get(it.uniqueId)!!.getId() },
                plugin.server.onlinePlayers.filter { !blueTeam.members.contains(it) && !redTeam.members.contains(it) }.map { SteamwarUser.get(it.uniqueId)!!.getId() }
            ))
    }

    enum class GameState(val listener: Listener, val lobbyName: String) {
        LOBBY(LobbyListener, "PRE_LEADER_SETUP"),
        STARTING(LobbyListener, "POST_SCHEM_SETUP"),
        RUNNING(IngameListener, "RUNNING"),
        END(DummyListener, "SPECTATE");
    }

    enum class WinReason {
        TIMEOUT,
        DESTROYED,
        LEAVE
    }
}