package moe.lava.neon.tests.api

import io.kotest.property.Arb
import io.kotest.property.arbitrary.long
import io.kotest.property.arbitrary.next
import io.kotest.property.arbitrary.single
import io.kotest.property.arbitrary.string
import io.kotest.property.arbitrary.stringPattern
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.MockRequestHandleScope
import io.ktor.client.engine.mock.respond
import io.ktor.client.engine.mock.respondError
import io.ktor.client.request.HttpResponseData
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.TextContent
import io.ktor.http.headersOf
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import moe.lava.neon.common.captcha.CaptchaRequest
import moe.lava.neon.common.captcha.CaptchaResponse
import moe.lava.neon.tests.api.mock.AuthResponse

@OptIn(ExperimentalSerializationApi::class)
private val JsonWithSnakecase = Json {
    namingStrategy = JsonNamingStrategy.SnakeCase
}
private val JsonHeader = headersOf(HttpHeaders.ContentType, "application/json")

val idArb = Arb.long(1e18.toLong(), 1e20.toLong())
// Good enough
val tokenArb = Arb.stringPattern("(mfa\\.[a-zA-Z0-9_-]{20,})|([a-zA-Z0-9_-]{23,28}\\.[a-zA-Z0-9_-]{6,7}\\.[a-zA-Z0-9_-]{38})")

class DiscordApiMock {
    private val fingerprints = mutableListOf<String>()
    private val logins = mutableMapOf<String, String>()
    private var captcha: Pair<CaptchaRequest, CaptchaResponse.Success> = generateCaptcha()

    var isCaptchaEnabled = false

    fun createLogin(email: String, password: String) {
        logins[email] = password
    }

    fun generateCaptcha(): Pair<CaptchaRequest, CaptchaResponse.Success> {
        val req = CaptchaRequest(
            listOf(Arb.string().single()),
            Arb.string().single(),
            Arb.string().single(),
            Arb.string().single(),
            Arb.string().single(),
            Arb.string().single(),
            true,
        )
        val res = CaptchaResponse.Success(Arb.string().single())
        captcha = req to res
        return req to res
    }

    val engine = MockEngine { req ->
        if (!req.url.toString().startsWith("https://discord.com/api/v9")) {
            return@MockEngine respondError(HttpStatusCode.NotFound)
        }
        if (isCaptchaEnabled) {
            if (req.headers["X-Captcha-Key"] != captcha.second.token) {
                return@MockEngine respondJson(JsonWithSnakecase.encodeToString(captcha.first), HttpStatusCode.BadRequest)
            }
        }
        val path = req.url.encodedPath.replaceFirst("/api/v9", "")
        return@MockEngine when (path) {
            "/experiments" -> {
                val fp = Arb.string(18..20, "123456789").single()
                fingerprints.add(fp)
                respondJson(AuthResponse.Experiments(fp))
            }
            "/auth/login" -> {
                val body = req.body as? TextContent
                    ?: return@MockEngine badReq("No body")
                val json = Json.parseToJsonElement(body.text).jsonObject
                val login = json["login"]?.jsonPrimitive?.content
                    ?: return@MockEngine badReq("No login")
                val password = json["password"]?.jsonPrimitive?.content
                    ?: return@MockEngine badReq("No password")

                if (logins[login] != password) {
                    return@MockEngine badReq("Unknown credentials")
                }

                respondJson(AuthResponse.Login(idArb.next(), tokenArb.next()))
            }
            else -> respondError(HttpStatusCode.NotFound)
        }
    }

    @Suppress("NOTHING_TO_INLINE")
    private inline fun MockRequestHandleScope.badReq(msg: String): HttpResponseData =
        respondError(HttpStatusCode.BadRequest, content = "[Neon] $msg")

    @Suppress("NOTHING_TO_INLINE")
    private inline fun MockRequestHandleScope.respondJson(content: String, status: HttpStatusCode = HttpStatusCode.OK): HttpResponseData =
        respond(content = content, status = status, headers = JsonHeader)
}
