package de.steamwar.tntleague.game

import de.steamwar.scoreboard.SWScoreboard
import de.steamwar.sql.Fight
import de.steamwar.sql.FightPlayer
import de.steamwar.sql.SteamwarUser
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.plugin
import de.steamwar.tntleague.util.*
import net.kyori.adventure.bossbar.BossBar
import net.kyori.adventure.sound.Sound
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.block.data.type.TNT
import org.bukkit.entity.Entity
import org.bukkit.entity.Item
import org.bukkit.entity.Player
import org.bukkit.entity.TNTPrimed
import org.bukkit.entity.Villager
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(TNTLeagueWorldConfig.blueTeam, TNTLeagueTeam.Team.BLUE)
    val redTeam = TNTLeagueTeam(TNTLeagueWorldConfig.redTeam, TNTLeagueTeam.Team.RED)

    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()

        plugin.server.broadcast(translate("gameStarted").success())

        val tnt = ItemStack(Material.TNT)

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

        spawnerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
            if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.blueTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
                spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, tnt)
                spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, DealerInventory.coins)
            }
            if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.redTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
                spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, tnt)
                spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, DealerInventory.coins)
            }
        }, 5, 10)

        timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
            gameTimeRemaining--
            if (gameTimeRemaining == 0) {
                draw(WinReason.TIMEOUT)
                return@bukkit
            }

            if (gameTimeRemaining % 300 == 0) {
                plugin.server.broadcast(translate("timeRemaining", (gameTimeRemaining / 60).toString().yellow()).basic())
                plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING.key, Sound.Source.MASTER, 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(Sound.sound(org.bukkit.Sound.ENTITY_ENDER_DRAGON_DEATH.key, Sound.Source.MASTER, 1f, 1f))
        }

        plugin.server.broadcast(translate("gameEnded").success())

        spawnerTask.cancel()

        var shutdown = 10

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

            plugin.server.broadcast(translate("shutdown", shutdown.toString().yellow()).basic())

            shutdown--
        }, 20, 20)
    }

    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() = if (blueTeam.leader == null) blueTeam else if (redTeam.leader == null) 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
            plugin.server.broadcast(translate("gameStarting", countdown.toString().yellow()).basic())
            val bar = BossBar.bossBar(translate("gameStart", countdown.toString().yellow()).gray(), (TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat(), BossBar.Color.GREEN, BossBar.Overlay.NOTCHED_10)
            plugin.server.onlinePlayers.forEach { bar.addViewer(it) }
            task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, {
                plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP.key, Sound.Source.MASTER, 1f, 1f)) }
                if (countdown-- == 0) {
                    plugin.server.onlinePlayers.forEach { it.hideBossBar(bar) }
                    task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null }
                    setup()
                } else {
                    bar.name(translate("gameStart", countdown.toString().yellow()).gray())
                    bar.progress((TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat())
                    plugin.server.onlinePlayers.filter { !it.activeBossBars().contains(bar) }.forEach { bar.addViewer(it) }
                }
            }, 20, 20)

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

    fun playerLeave(player: Player) {
        blueTeam.invites.remove(player)
        redTeam.invites.remove(player)
        getTeam(player)?.apply {
            members.remove(player)
            if (leader == player) {
                win(this.opposite, WinReason.LEAVE)
            }
        }
    }

    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 }
            plugin.server.onlinePlayers.forEach { p -> p.activeBossBars().forEach { it.removeViewer(p) } }
            state = GameState.LOBBY
        }
    }

    fun win(tntLeagueTeam: TNTLeagueTeam, reason: WinReason) {
        if (state != GameState.RUNNING) return
        end()
        plugin.server.broadcast(translate("teamWin", translate(tntLeagueTeam.name).color(tntLeagueTeam.color)).success())
        statistic(tntLeagueTeam, reason)
        explode(tntLeagueTeam.opposite)
    }

    fun draw(reason: WinReason) {
        if (state != GameState.RUNNING) return
        end()
        plugin.server.broadcast(translate("draw").success())
        statistic(null, reason)
    }

    fun explode(team: TNTLeagueTeam) {
        Area(team.config.spawnLocation.clone().add(20.0, 30.0, 20.0), team.config.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
                }
            }
    }

    private fun statistic(winTeam: TNTLeagueTeam?, reason: WinReason) {
        val fightId = Fight.create(
            "TNTLeague",
            world.name,
            start,
            TNTLeagueConfig.config.gameTime - gameTimeRemaining,
            SteamwarUser.get(blueTeam.leader!!.uniqueId).id,
            SteamwarUser.get(redTeam.leader!!.uniqueId).id,
            null,
            null,
            when (winTeam) {
                blueTeam -> 1
                redTeam -> 2
                else -> 0
            },
            when (reason) {
                WinReason.TIMEOUT -> "TIMEOUT"
                WinReason.DESTROYED -> "DESTROYED"
                WinReason.LEAVE -> "LEAVE"
            }
        )

        addTeamMember(blueTeam, fightId)
        addTeamMember(redTeam, fightId)
    }

    private fun addTeamMember(team: TNTLeagueTeam, fightId: Int) {
        team.members.filter { team.leader != it }
            .forEach {
                FightPlayer.create(
                    fightId,
                    SteamwarUser.get(it.uniqueId).id,
                    team == blueTeam,
                    "TNTLeague",
                    0,
                    false
                )
            }
    }

    enum class GameState(val listener: Listener) {
        LOBBY(LobbyListener),
        STARTING(LobbyListener),
        RUNNING(IngameListener),
        END(DummyListener);
    }

    enum class WinReason {
        TIMEOUT,
        DESTROYED,
        LEAVE
    }
}