package moe.lava.neon.api

import co.touchlab.kermit.Logger
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.plugin
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.userAgent
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.appendAll
import kotlinx.serialization.ExperimentalSerializationApi
import moe.lava.neon.common.captcha.CaptchaRequest
import moe.lava.neon.common.captcha.CaptchaResponse

class ApiClient internal constructor(
    engine: HttpClientEngine,
    assertSuccess: Boolean,
) {
    constructor() : this(HttpClient().engine, true)

    private val logger = Logger.withTag("neon.core.api/client")

    private var captchaHandler: (suspend (CaptchaRequest) -> CaptchaResponse)? = null

    fun setCaptchaHandler(handler: suspend (CaptchaRequest) -> CaptchaResponse) {
        this.captchaHandler = handler
    }

    @OptIn(ExperimentalSerializationApi::class)
    internal val client = HttpClient(engine) {
        expectSuccess = assertSuccess
        install(ContentNegotiation) {
            json(ApiConstants.json)
        }
        install(WebSockets)
        install(HttpCookies)
        defaultRequest {
            url("https://discord.com/api/v9/")
            contentType(ContentType.Application.Json)
            userAgent(ApiConstants.userAgent)
            headers.appendAll(ApiConstants.baseHeaders)
        }
    }.apply {
        plugin(HttpSend).intercept { req ->
            logger.d { "Intercepting ${req.url.buildString()}" }
            val call = execute(req)
            logger.d { "recv ${call.response.bodyAsText()}" }
            if (call.response.status.value != 400) return@intercept call
            logger.d { "Found 400 response: ${call.response.bodyAsText()}" }
            val captchaRequest = runCatching { call.response.body<CaptchaRequest>() }
                .getOrNull()
                ?: return@intercept call

            logger.d { "Starting captcha flow for: $captchaRequest" }

            val captcha = captchaHandler
            if (captcha == null) {
                logger.w { "Captcha handler not found, passing through!" }
                return@intercept call
            }

            val solved = captcha(captchaRequest)
            logger.d { "Captcha solved $solved" }
            if (solved !is CaptchaResponse.Success) {
                val failure = solved as CaptchaResponse.Failed
                logger.w(failure.error) { "Captcha failed" }
                return@intercept call
            }

            logger.d { "Refiring" }
            req.apply {
                header("X-Captcha-Key", solved.token)
                if (captchaRequest.captchaSessionId != null) {
                    header("X-Captcha-Session-Id", captchaRequest.captchaSessionId)
                }
                if (captchaRequest.captchaRqtoken != null) {
                    header("X-Captcha-Rqtoken", captchaRequest.captchaRqtoken)
                }
            }.let { execute(it) }
        }
    }
}
