package de.trainingstagebuch.app.network

import android.content.Context
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import de.trainingstagebuch.app.BuildConfig
import de.trainingstagebuch.app.core.SessionManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.runBlocking

object NetworkModule {
    private val DEFAULT_BASE_URL = BuildConfig.API_BASE_URL
    private val unauthorizedCodes = setOf(
        "ERROR_UNAUTHORIZED",
        "ERROR_FORBIDDEN",
        "ERROR_SESSION_EXPIRED"
    )

    private val json = Json {
        ignoreUnknownKeys = true
        explicitNulls = false
    }

    @Volatile
    private var authToken: String? = null

    @Volatile
    private var userId: String? = null

    @Volatile
    private var sessionManager: SessionManager? = null

    private val initialized = AtomicBoolean(false)
    private val sessionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    private val loggingInterceptor = HttpLoggingInterceptor().apply {
        level = if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor.Level.BASIC
        } else {
            HttpLoggingInterceptor.Level.NONE
        }
    }

    private val okHttpClient = OkHttpClient.Builder()
        .addInterceptor { chain ->
            val requestBuilder = chain.request().newBuilder()
            authToken?.takeIf { it.isNotBlank() }?.let { requestBuilder.addHeader("authcode", it) }
            userId?.takeIf { it.isNotBlank() }?.let { requestBuilder.addHeader("userid", it) }
            val response = chain.proceed(requestBuilder.build())
            if (isUnauthorizedResponse(response)) {
                runBlocking {
                    sessionManager?.clear()
                }
                AuthEventBus.notifyUnauthorized()
            }
            response
        }
        .addInterceptor(loggingInterceptor)
        .build()

    fun initialize(context: Context) {
        if (!initialized.compareAndSet(false, true)) return
        val manager = SessionManager(context.applicationContext)
        sessionManager = manager
        sessionScope.launch {
            manager.session.collectLatest { session ->
                authToken = session.token
                userId = session.username
            }
        }
    }

    fun updateAuthSession(token: String?, username: String?) {
        authToken = token
        userId = username
    }

    fun createApiService(baseUrl: String = DEFAULT_BASE_URL): ApiService {
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
            .build()

        return retrofit.create(ApiService::class.java)
    }

    private fun isUnauthorizedResponse(response: Response): Boolean {
        if (response.code !in setOf(401, 403)) return false
        val bodyText = runCatching { response.peekBody(64 * 1024).string() }.getOrNull()
        if (bodyText.isNullOrBlank()) return true

        val parsedCode = runCatching {
            json.parseToJsonElement(bodyText)
                .jsonObject["code"]
                ?.jsonPrimitive
                ?.content
        }.getOrNull()

        if (parsedCode.isNullOrBlank()) return true
        if (parsedCode in unauthorizedCodes) return true

        return parsedCode.contains("UNAUTHORIZED", ignoreCase = true) ||
            parsedCode.contains("FORBIDDEN", ignoreCase = true)
    }
}
