package com.nisemoe.nise.integrations

import com.nisemoe.nise.osu.Mod
import com.nisemoe.nise.scheduler.ImportScores
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.concurrent.CompletableFuture

@Service
class CircleguardService {

    @Value("\${CIRCLEGUARD_API_URL:http://localhost:5000}")
    private var apiUrl: String = "http://localhost:5000"

    private val httpClient: HttpClient = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .build()

    private val serializer = Json

    @Serializable
    data class ReplayRequest(
        val replay_data: String,
        val beatmap_data: String,
        val mods: Int
    )

    @Serializable
    data class ScoreJudgement(
        val time: Double,
        val x: Double,
        val y: Double,
        val type: JudgementType,
        val distance_center: Double,
        val distance_edge: Double,
        val error: Double
    )

    @Serializable
    enum class JudgementType {
        @SerialName("Hit300")
        THREE_HUNDRED,

        @SerialName("Hit100")
        ONE_HUNDRED,

        @SerialName("Hit50")
        FIFTY,

        @SerialName("Miss")
        MISS
    }

    @Serializable
    data class ReplayResponse(
        val ur: Double?,
        val adjusted_ur: Double?,
        val frametime: Double?,
        val edge_hits: Int?,
        val snaps: Int?,
        var mean_error: Double?,
        var error_variance: Double?,
        var error_standard_deviation: Double?,
        var minimum_error: Double?,
        var maximum_error: Double?,
        var error_range: Double?,
        var error_coefficient_of_variation: Double?,
        var error_kurtosis: Double?,
        var error_skewness: Double?,

        val keypresses_times: List<Double>?,
        val keypresses_median: Double?,
        val keypresses_median_adjusted: Double?,
        val keypresses_standard_deviation: Double?,
        val keypresses_standard_deviation_adjusted: Double?,

        val sliderend_release_times: List<Double>?,
        val sliderend_release_median: Double?,
        val sliderend_release_median_adjusted: Double?,
        val sliderend_release_standard_deviation: Double?,
        val sliderend_release_standard_deviation_adjusted: Double?,

        val judgements: List<ScoreJudgement>
    )

    fun postProcessReplay(replayResponse: ReplayResponse, mods: Int = 0) {
        var conversionFactor = 1.0
        if(Mod.containsMod(mods, Mod.DT)) {
            conversionFactor = (1 / 1.5)
        } else if(Mod.containsMod(mods, Mod.HT)) {
            conversionFactor = (1 / 0.75)
        }

        replayResponse.mean_error = replayResponse.mean_error?.times(conversionFactor)
        replayResponse.error_variance = replayResponse.error_variance?.times(conversionFactor)
        replayResponse.error_standard_deviation = replayResponse.error_standard_deviation?.times(conversionFactor)
        replayResponse.error_coefficient_of_variation = replayResponse.error_coefficient_of_variation?.times(conversionFactor)
        replayResponse.error_kurtosis = replayResponse.error_kurtosis?.times(conversionFactor)
        replayResponse.error_skewness = replayResponse.error_skewness?.times(conversionFactor)
    }

    fun processReplay(replayData: String, beatmapData: String, mods: Int = 0): CompletableFuture<ReplayResponse> {
        val requestUri = "$apiUrl/replay"

        val request = ReplayRequest(
            replay_data = replayData,
            beatmap_data = beatmapData,
            mods = mods,
        )

        // Serialize the request object to JSON
        val requestBody = serializer.encodeToString(ReplayRequest.serializer(), request)

        val httpRequest = HttpRequest.newBuilder()
            .uri(URI.create(requestUri))
            .header("Content-Type", "application/json") // Set content type to application/json
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build()

        return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
            .thenApply { response: HttpResponse<String> ->
                if (response.statusCode() == 200) {
                    // Deserialize the JSON response to ReplayResponse object
                    val decodedReplay = serializer.decodeFromString(ReplayResponse.serializer(), response.body())
                    postProcessReplay(decodedReplay, mods)
                    decodedReplay
                } else {
                    throw RuntimeException("Failed to process replay: ${response.body()}")
                }
            }
    }

    @Serializable
    data class SimilarityRequest(
        val replays: List<ImportScores.ReplayDto>,
    )

    @Serializable
    data class SimilarityResponse(
        val result: List<SimilarityResponseEntry>
    )

    @Serializable
    data class SimilarityResponseEntry(
        val replay_id_1: Long,
        val replay_id_2: Long,

        val similarity: Double,
        val correlation: Double
    )

    fun processSimilarity(replays: List<ImportScores.ReplayDto>): CompletableFuture<SimilarityResponse> {
        val requestUri = "$apiUrl/similarity"

        val request = SimilarityRequest(
            replays = replays
        )

        // Serialize the request object to JSON
        val requestBody = serializer.encodeToString(SimilarityRequest.serializer(), request)

        val httpRequest = HttpRequest.newBuilder()
            .uri(URI.create(requestUri))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build()

        return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
            .thenApply { response: HttpResponse<String> ->
                if (response.statusCode() == 200) {
                    serializer.decodeFromString(SimilarityResponse.serializer(), response.body())
                } else {
                    throw RuntimeException("Failed to process replay: ${response.body()}")
                }
            }
    }

}