package moe.lava.neon.core.api

import co.touchlab.kermit.Logger
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import io.ktor.client.HttpClient
import io.ktor.client.call.body
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.userAgent
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.appendAll
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import moe.lava.neon.core.api.captcha.CaptchaRequest
import moe.lava.neon.core.api.captcha.CaptchaResponse

@SingleIn(AppScope::class)
@Inject
class ApiClient {
    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)
    val client = HttpClient {
        expectSuccess = true
        install(ContentNegotiation) {
            json(ApiConstants.json)
        }
        install(WebSockets)
        install(HttpCookies)
        defaultRequest {
            url("https://discord.com/api/v9/")
            userAgent(ApiConstants.userAgent)
            headers.appendAll(ApiConstants.baseHeaders)
        }
    }.apply {
        plugin(HttpSend).intercept { req ->
            logger.d { "Intercepting ${req.url.buildString()}" }
            val call = execute(req)
            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) }
        }
    }
}

@OptIn(ExperimentalSerializationApi::class)
fun buildApiClient() = HttpClient {
    expectSuccess = true
    install(ContentNegotiation) {
        json(Json {
            namingStrategy = JsonNamingStrategy.SnakeCase
            ignoreUnknownKeys = true
        })
    }
    install(WebSockets)
    install(HttpCookies)
    defaultRequest {
        url("https://discord.com/api/v9/")
        headers.appendAll(ApiConstants.baseHeaders)
    }
}
