package com.nisemoe.nise.search

import com.nisemoe.generated.tables.references.BEATMAPS
import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.USERS
import com.nisemoe.nise.service.AuthService
import org.jooq.Field
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class SearchSchemaController(
    private val authService: AuthService,
) {

    companion object {

        val internalFields = listOf(
            // User fields
            InternalSchemaField("user_id", "ID", Category.user, Type.number, false, "unique identifier for a user", databaseField = USERS.USER_ID),
            InternalSchemaField("user_username", "Username", Category.user, Type.string, true, "user's name", databaseField = USERS.USERNAME),
            InternalSchemaField("user_join_date", "Join Date", Category.user, Type.datetime, false, "when the user joined", databaseField = USERS.JOIN_DATE),
            InternalSchemaField("user_country", "Country", Category.user, Type.flag, true, "user's country flag", databaseField = USERS.COUNTRY),
            InternalSchemaField("user_country_rank", "Country Rank", Category.user, Type.number, false, "ranking within user's country", databaseField = USERS.COUNTRY_RANK),
            InternalSchemaField("user_rank", "Rank", Category.user, Type.number, false, "global ranking", databaseField = USERS.RANK),
            InternalSchemaField("user_pp_raw", "User PP", Category.user, Type.number, false, "performance points", databaseField = USERS.PP_RAW),
            InternalSchemaField("user_accuracy", "User Accuracy", Category.user, Type.number, false, "hit accuracy percentage", databaseField = USERS.ACCURACY),
            InternalSchemaField("user_playcount", "Playcount", Category.user, Type.number, false, "total plays", databaseField = USERS.PLAYCOUNT),
            InternalSchemaField("user_total_score", "Total Score", Category.user, Type.number, false, "cumulative score", databaseField = USERS.TOTAL_SCORE),
            InternalSchemaField("user_ranked_score", "Ranked Score", Category.user, Type.number, false, "score from ranked maps", databaseField = USERS.RANKED_SCORE),
            InternalSchemaField("user_seconds_played", "Play Time", Category.user, Type.playtime, false, "total play time in seconds", databaseField = USERS.SECONDS_PLAYED),
            InternalSchemaField("user_count_300", "300s", Category.user, Type.number, false, "number of 300 hits", databaseField = USERS.COUNT_300),
            InternalSchemaField("user_count_100", "100s", Category.user, Type.number, false, "number of 100 hits", databaseField = USERS.COUNT_100),
            InternalSchemaField("user_count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50),
            InternalSchemaField("user_count_miss", "Misses", Category.user, Type.number, false, "missed hits", databaseField = USERS.COUNT_MISS),

            // Score fields
            InternalSchemaField("is_banned", "Banned", Category.score, Type.boolean, false, "has to score been deleted?", databaseField = SCORES.IS_BANNED),
            InternalSchemaField("id", "ID", Category.score, Type.number, false, "unique identifier for a score", databaseField = SCORES.ID),
            InternalSchemaField("beatmap_id", "Beatmap ID", Category.score, Type.number, false, "identifies the beatmap", databaseField = SCORES.BEATMAP_ID),
            InternalSchemaField("count_300", "300s", Category.score, Type.number, false, "number of 300 hits in score", databaseField = SCORES.COUNT_300),
            InternalSchemaField("count_100", "100s", Category.score, Type.number, false, "number of 100 hits in score", databaseField = SCORES.COUNT_100),
            InternalSchemaField("count_50", "50s", Category.score, Type.number, false, "number of 50 hits in score", databaseField = SCORES.COUNT_50),
            InternalSchemaField("count_miss", "Misses", Category.score, Type.number, false, "missed hits in score", databaseField = SCORES.COUNT_MISS),
            InternalSchemaField("date", "Date", Category.score, Type.datetime, true, "when score was achieved", databaseField = SCORES.DATE),
            InternalSchemaField("max_combo", "Max Combo", Category.score, Type.number, false, "highest combo in score", databaseField = SCORES.MAX_COMBO),
            InternalSchemaField("mods", "Mods", Category.score, Type.number, false, "game modifiers used", databaseField = SCORES.MODS),
            InternalSchemaField("perfect", "Perfect", Category.score, Type.boolean, false, "if score is a full combo", databaseField = SCORES.PERFECT),
            InternalSchemaField("pp", "Score PP", Category.score, Type.number, true, "performance points for score", databaseField = SCORES.PP),
            InternalSchemaField("rank", "Rank", Category.score, Type.grade, false, "score grade", databaseField = SCORES.RANK),
            InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay", databaseField = SCORES.REPLAY_ID),
            InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value", databaseField = SCORES.SCORE),
            InternalSchemaField("ur", "UR", Category.metrics, Type.number, false, "unstable rate", databaseField = SCORES.UR),
            InternalSchemaField("frametime", "Frame Time", Category.metrics, Type.number, false, "median frame time during play", databaseField = SCORES.FRAMETIME),
            InternalSchemaField("edge_hits", "Edge Hits", Category.metrics, Type.number, false, "hits at the edge of the hitobject (<1px)", databaseField = SCORES.EDGE_HITS),
            InternalSchemaField("snaps", "Snaps", Category.metrics, Type.number, false, "rapid cursor movements", databaseField = SCORES.SNAPS),
            InternalSchemaField("adjusted_ur", "Adj. UR", Category.metrics, Type.number, true, "adjusted unstable rate", databaseField = SCORES.ADJUSTED_UR),
            InternalSchemaField("mean_error", "Mean Error", Category.metrics, Type.number, false, "average timing error", databaseField = SCORES.MEAN_ERROR),
            InternalSchemaField("error_variance", "Error Var.", Category.metrics, Type.number, false, "variability of error in scores", databaseField = SCORES.ERROR_VARIANCE),
            InternalSchemaField("error_standard_deviation", "Error SD", Category.metrics, Type.number, false, "standard deviation of error", databaseField = SCORES.ERROR_STANDARD_DEVIATION),
            InternalSchemaField("minimum_error", "Min Error", Category.metrics, Type.number, false, "smallest error recorded", databaseField = SCORES.MINIMUM_ERROR),
            InternalSchemaField("maximum_error", "Max Error", Category.metrics, Type.number, false, "largest error recorded", databaseField = SCORES.MAXIMUM_ERROR),
            InternalSchemaField("error_range", "Error Range", Category.metrics, Type.number, false, "range between min and max error", databaseField = SCORES.ERROR_RANGE),
            InternalSchemaField("error_coefficient_of_variation", "Error CV", Category.metrics, Type.number, false, "relative variability of error", databaseField = SCORES.ERROR_COEFFICIENT_OF_VARIATION),
            InternalSchemaField("error_kurtosis", "Kurtosis", Category.metrics, Type.number, false, "peakedness of error distribution", databaseField = SCORES.ERROR_KURTOSIS),
            InternalSchemaField("error_skewness", "Skewness", Category.metrics, Type.number, false, "asymmetry of error distribution", databaseField = SCORES.ERROR_SKEWNESS),
            InternalSchemaField("keypresses_median_adjusted", "KP Median Adj.", Category.metrics, Type.number, false, "median of adjusted keypresses", isPrivileged = true, databaseField = SCORES.KEYPRESSES_MEDIAN_ADJUSTED),
            InternalSchemaField("keypresses_standard_deviation_adjusted", "KP std. Adj.", Category.metrics, Type.number, false, "std. dev of adjusted keypresses", isPrivileged = true, databaseField = SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED),
            InternalSchemaField("sliderend_release_median_adjusted", "Sliderend Median Adj.", Category.metrics, Type.number, false, "median of adjusted sliderend releases", isPrivileged = true, databaseField = SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED),
            InternalSchemaField("sliderend_release_standard_deviation_adjusted", "Sliderend std. Adj.", Category.metrics, Type.number, false, "std. dev of adjusted sliderend releases", isPrivileged = true, databaseField = SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED),

            // Beatmap fields
            InternalSchemaField("beatmap_artist", "Artist", Category.beatmap, Type.string, false, "artist of the beatmap", databaseField = BEATMAPS.ARTIST),
            InternalSchemaField("beatmap_beatmapset_id", "Set ID", Category.beatmap, Type.number, false, "id of the beatmap set", databaseField = BEATMAPS.BEATMAPSET_ID),
            InternalSchemaField("beatmap_creator", "Creator", Category.beatmap, Type.string, false, "creator of the beatmap", databaseField = BEATMAPS.CREATOR),
            InternalSchemaField("beatmap_source", "Source", Category.beatmap, Type.string, false, "source of the beatmap music", databaseField = BEATMAPS.SOURCE),
            InternalSchemaField("beatmap_star_rating", "Stars", Category.beatmap, Type.number, false, "(★) difficulty rating of the beatmap", databaseField = BEATMAPS.STAR_RATING),
            InternalSchemaField("beatmap_title", "Title", Category.beatmap, Type.string, false, "title of the beatmap", databaseField = BEATMAPS.TITLE),
            InternalSchemaField("beatmap_version", "Version", Category.beatmap, Type.string, false, "version or difficulty name of the beatmap", databaseField = BEATMAPS.VERSION),
        )

    }

    data class InternalSchemaField(
        val name: String,
        val shortName: String,
        val category: Category,
        val type: Type,
        val active: Boolean,
        val description: String,

        val isPrivileged: Boolean = false,
        val databaseField: Field<*>? = null
    )

    data class SchemaField(
        val name: String,
        val shortName: String,
        val category: Category,
        val type: Type,
        val active: Boolean,
        val description: String
    )

    enum class Category {
        user, score, beatmap, metrics
    }

    enum class Type {
        number, string, flag, grade, boolean, datetime, playtime
    }

    data class SearchSchema(
        val fields: List<SchemaField>
    )

    @GetMapping("search/schema")
    fun getSearchSchema(): ResponseEntity<SearchSchema> {
        // Map to SchemaField
        val isUserAdmin = authService.isAdmin()
        val fields = internalFields
            .filter {
                return@filter !(it.isPrivileged && !isUserAdmin)
            }
            .map {
            SchemaField(
                name = it.name,
                shortName = it.shortName,
                category = it.category,
                type = it.type,
                active = it.active,
                description = it.description
            )
        }

        val schema = SearchSchema(fields)
        return ResponseEntity.ok(schema)
    }

}