package com.jaytux.simd.server

import com.jaytux.simd.data.*
import com.jaytux.simd.server.RouteCache.register
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.*

@Serializable
data class IntrinsicSummary(@Serializable(with = UUIDSerializer::class) val id: UUID, val name: String)

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

@Serializable
data class Instruction(val mnemonic: String, val xed: String, val form: String?)

@Serializable
data class PlatformPerformance(val platform: String, val latency: Float?, val throughput: Float?)

@Serializable
data class IntrinsicDetails(
    @Serializable(with = UUIDSerializer::class) val id: UUID,
    val name: String,
    val returnType: String,
    val returnVar: String?,
    val description: String,
    val operations: String?,
    val category: String,
    val cpuid: String?,
    val tech: String,
    val params: List<Param>,
    val instructions: List<Instruction>?,
    val performance: List<PlatformPerformance>?
)

@Serializable
data class Versioning(
    val intelVersion: String, val intelUpdate: LocalDate, val scrapeDate: LocalDate
)

fun Routing.installGetAll() {
    getPagedUrl("/all", "Get a quick overview of all endpoints", { 100 }, { IntrinsicSummary(it.id.value, it.mnemonic) }) {
            Intrinsic.all().orderAsc(Intrinsics.mnemonic)
        }

    getPagedUrl("/cpuid", "Get a list of all CPUIDs", { 100 }, { it.name }) { CPUID.all().orderAsc(CPUIDs.name) }
    getPagedUrl("/tech", "Get a list of all technologies", { 100 }, { it.name }) { Tech.all().orderAsc(Techs.name) }
    getPagedUrl("/category", "Get a list of all intrinsic categories", { 100 }, { it.name }) { Category.all().orderAsc(Categories.name) }
    getPagedUrl("/types", "Get a list of all used C/C++ types", { 100 }, { it.name }) { CppType.all().orderAsc(CppTypes.name) }
}

fun Routing.installSearch() {
    getPagedRequest("/search", "Search for intrinsics matching certain filters", { 100 }, { IntrinsicSummary(it[Intrinsics.id].value, it[Intrinsics.mnemonic]) }) {
        val name = call.request.queryParameters["name"]
        val returnType = call.request.queryParameters["return"]?.let {
            CppType.find { CppTypes.name eq it }.firstOrNull()
                ?: throw HttpError("Unknown return type: $it")
        }
        val cpuid = call.request.queryParameters["cpuid"]?.let {
            CPUID.find { CPUIDs.name eq it }.firstOrNull()
                ?: throw HttpError("Unknown CPUID: $it")
        }
        val tech = call.request.queryParameters["tech"]?.let {
            Tech.find { Techs.name eq it }.firstOrNull()
                ?: throw HttpError("Unknown tech: $it")
        }
        val category = call.request.queryParameters["category"]?.let {
            Category.find { Categories.name eq it }.firstOrNull()
                ?: throw HttpError("Unknown category: $it")
        }
        val desc = call.request.queryParameters["desc"]

        var results = Intrinsics.selectAll()
        name?.let { results = results.where { Intrinsics.mnemonic like "%$it%" } }
        returnType?.let { results = results.where { Intrinsics.returnType eq it.id } }
        cpuid?.let { results = results.where { Intrinsics.cpuid eq it.id } }
        tech?.let { results = results.where { Intrinsics.tech eq it.id } }
        category?.let { results = results.where { Intrinsics.category eq it.id } }
        desc?.let { results = results.where { Intrinsics.description like "%$it%" } }

        results.orderAsc(Intrinsics.mnemonic)
    }
}

fun Routing.installDetails() {
    register(HttpMethod.Get, "/details/{id}", "Get details of an intrinsic by its ID")
    get("/details/{id}") {
        runCatching {
            transaction {
                val id = call.parameters["id"]?.let {
                    try {
                        UUID.fromString(it)
                    }
                    catch(e: IllegalArgumentException) {
                        throw HttpError("Invalid UUID: $it")
                    }
                } ?: throw HttpError("Missing or invalid ID")
                val intrinsic = Intrinsic.findById(id)
                    ?: throw HttpError("Unknown intrinsic: $id")

                IntrinsicDetails(
                    id = intrinsic.id.value,
                    name = intrinsic.mnemonic,
                    returnType = intrinsic.returnType.name,
                    returnVar = intrinsic.returnVar,
                    description = intrinsic.description,
                    operations = intrinsic.operations,
                    category = intrinsic.category.name,
                    cpuid = intrinsic.cpuid?.name,
                    tech = intrinsic.tech.name,
                    params = intrinsic.arguments.orderAsc(IntrinsicArguments.index)
                        .map { Param(it.name, it.type.name) },
                    instructions = intrinsic.instructions.emptyToNull()
                        ?.map { Instruction(it.mnemonic, it.xed, it.form) },
                    performance = intrinsic.instructions.firstOrNull()?.let {
                        (Performances innerJoin Platforms).selectAll().where {
                            Performances.instruction eq it.id
                        }.map {
                            PlatformPerformance(
                                platform = it[Platforms.name],
                                latency = it[Performances.latency],
                                throughput = it[Performances.throughput]
                            )
                        }
                    }
                )
            }
        }
    }
}

fun Routing.installVersion() {
    register(HttpMethod.Get, "/version", "Get the current version of the data")
    get("/version") {
        val upd = Loader.DatedVersion.fromPrefs()
        call.respond(Versioning(upd.version.toString(), intelUpdate = upd.releaseDate, upd.updateDate))
    }
}