package xyz.etztech.stonks.api

import io.javalin.Javalin
import java.time.Duration
import java.time.Instant
import kotlinx.coroutines.*
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.*
import xyz.etztech.stonks.dsl.LiveStatistics
import xyz.etztech.stonks.dsl.Statistics

private var statisticsCache = ""

private var statisticsCacheTimestamp = Instant.now()

private var playersCache = ""

private var playersCacheTimestamp = Instant.now()

fun initApiServer(apiServerPort: Int, database: Database) {
    val app = Javalin.create().start(apiServerPort)
    println("Javalin web server started")

    app.get("/statistics") { ctx ->
        run {
            if (statisticsCache.isEmpty() or
                    (Duration.between(Instant.now(), statisticsCacheTimestamp) >
                            Duration.ofHours(1))) {
                transaction(database) {
                    addLogger(StdOutSqlLogger)

                    val statistics =
                            LiveStatistics.slice(LiveStatistics.type, LiveStatistics.name)
                                    .selectAll()
                                    .groupBy(LiveStatistics.type, LiveStatistics.name)
                                    .map {
                                        Statistic(it[LiveStatistics.type], it[LiveStatistics.name])
                                    }

                    statisticsCache = Json.encodeToString(statistics)
                }
            }

            ctx.result(statisticsCache)
        }
    }

    app.get("/statistics/:type/:name") { ctx ->
        run {
            transaction(database) {
                addLogger(StdOutSqlLogger)
                val statistics =
                        LiveStatistics.slice(
                                        LiveStatistics.type,
                                        LiveStatistics.name,
                                        LiveStatistics.playerId,
                                        LiveStatistics.value,
                                        LiveStatistics.rank)
                                .select {
                                    LiveStatistics.type.eq(ctx.pathParam("type")) and
                                            LiveStatistics.name.eq(ctx.pathParam("name"))
                                }
                                .orderBy(LiveStatistics.rank, SortOrder.ASC)
                                .map {
                                    StatisticValue(
                                            it[LiveStatistics.playerId],
                                            it[LiveStatistics.type],
                                            it[LiveStatistics.name],
                                            it[LiveStatistics.value],
                                            it[LiveStatistics.rank])
                                }

                ctx.result(Json { prettyPrint = true }.encodeToString(statistics))
            }
        }
    }

    app.get("/players") { ctx ->
        run {
            if (playersCache.isEmpty() or
                    (Duration.between(Instant.now(), playersCacheTimestamp) >
                            Duration.ofHours(1))) {
                transaction(database) {
                    addLogger(StdOutSqlLogger)

                    val players =
                            LiveStatistics.slice(LiveStatistics.playerId)
                                    .selectAll()
                                    .groupBy(LiveStatistics.playerId)
                                    .map { it[LiveStatistics.playerId] }
                    playersCache = Json.encodeToString(players)
                }
            }

            ctx.result(playersCache)
        }
    }

    app.get("/players/:playerId") { ctx ->
        run {
            transaction(database) {
                addLogger(StdOutSqlLogger)

                val statistics =
                        LiveStatistics.slice(
                                        LiveStatistics.type,
                                        LiveStatistics.name,
                                        LiveStatistics.playerId,
                                        LiveStatistics.value,
                                        LiveStatistics.rank)
                                .select { LiveStatistics.playerId.eq(ctx.pathParam("playerId")) }
                                .map {
                                    StatisticValue(
                                            it[LiveStatistics.playerId],
                                            it[LiveStatistics.type],
                                            it[LiveStatistics.name],
                                            it[LiveStatistics.value],
                                            it[LiveStatistics.rank])
                                }

                ctx.result(Json { prettyPrint = true }.encodeToString(statistics))
            }
        }
    }

    app.get("/players/:playerId/:type/:name") { ctx ->
        run {
            transaction(database) {
                addLogger(StdOutSqlLogger)
                val statistics =
                        Statistics.slice(
                                        Statistics.type,
                                        Statistics.name,
                                        Statistics.playerId,
                                        Statistics.timestamp,
                                        Statistics.value)
                                .select {
                                    Statistics.playerId.eq(ctx.pathParam("playerId")) and
                                            Statistics.type.eq(ctx.pathParam("type")) and
                                            Statistics.name.eq(ctx.pathParam("name"))
                                }
                                .orderBy(Statistics.timestamp, SortOrder.DESC)
                                .map {
                                    HistoricalStatisticValue(
                                            it[Statistics.playerId],
                                            it[Statistics.type],
                                            it[Statistics.name],
                                            it[Statistics.timestamp].toString(),
                                            it[Statistics.value])
                                }

                ctx.result(Json { prettyPrint = true }.encodeToString(statistics))
            }
        }
    }
}

@Serializable data class Statistic(val type: String, val name: String)

@Serializable
data class StatisticValue(
        val playerId: String,
        val type: String,
        val name: String,
        val value: Long,
        val rank: Int
)

@Serializable
data class HistoricalStatisticValue(
        val playerId: String,
        val type: String,
        val name: String,
        val timestamp: String,
        val value: Long
)
