package moe.lava.neon.tests.api

import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.core.spec.style.funSpec
import io.kotest.core.spec.style.scopes.FunSpecContainerScope
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldBeOneOf
import io.kotest.matchers.collections.shouldContainAllInAnyOrder
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.ktor.client.statement.request
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import moe.lava.neon.api.ApiResponse
import kotlin.io.encoding.Base64

fun <T> endpointTestFactory(
    endpoint: String,
    testName: String?,
    response: suspend () -> ApiResponse<T>,
    also: suspend FunSpecContainerScope.(response: ApiResponse<T>) -> Unit,
) = funSpec {
    context(testName ?: endpoint) {
        val res = response()
        val req = res.response.request

        test("has correct base url") {
            req.url.toString().startsWith("https://discord.com/api/v9/") shouldBe true
        }
        test("uses correct endpoint") {
            req.url.encodedPath.replace("/api/v9/", "") shouldBe endpoint
        }
        test("has valid super props") {
            val props = req.headers["X-Super-Properties"]
            props.shouldNotBeNull()
            val decoded = withClue("should be decodable") {
                shouldNotThrowAny {
                    Base64.decode(props).decodeToString()
                }
            }
            val parsed = withClue("should be parsable") {
                shouldNotThrowAny {
                    Json.parseToJsonElement(decoded).jsonObject
                }
            }
            withClue("has props") {
                parsed.keys shouldContainAllInAnyOrder setOf(
                    "os",
                    "browser",
                    "browser_user_agent",
                    "browser_version",
                    "client_build_number",
                    "release_channel",
                    "system_locale",
                )
            }
            val userAgent = withClue("has valid user agent") {
                val agent = parsed["browser_user_agent"]?.jsonPrimitive
                agent?.isString.shouldBeTrue()
                agent.content
            }
            withClue("has matching user agent") {
                userAgent shouldBeOneOf setOf(req.headers["User-Agent"], "")
            }
        }
        test("has correct body") {
            shouldNotThrowAny { res.body() }
        }
        also(res)
    }
}

fun <T> FunSpec.withFactory(
    testName: String? = null,
    endpoint: String,
    response: suspend () -> ApiResponse<T>,
    also: suspend FunSpecContainerScope.(response: ApiResponse<T>) -> Unit = {},
) {
    include(endpointTestFactory(endpoint, testName, response, also))
}

