package com.zaojiao.app.core.http.interceptor

import android.util.Log
import com.zaojiao.app.core.auth.data.AuthRepository
import com.zaojiao.app.core.common.remote.di.ApplicationScope
import com.zaojiao.app.core.http.common.NetHeaders
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import javax.inject.Inject

class AuthInterceptor @Inject constructor(
    private val authRepository: AuthRepository,
    @ApplicationScope val coroutineScope: CoroutineScope,
) : Interceptor {
    companion object {
        const val CODE_UNAUTHORIZED = 401
        const val CODE_FORBIDDEN = 403
    }

    private val mutex = Mutex()

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request().also { debug(1, it.toString()) }

        if (NetHeaders.TOKEN_TYPE_NO in request.headers(NetHeaders.KEY)) {
            return chain.processWithoutToken(request)
        }

        val token = runBlocking(Dispatchers.IO) {
            authRepository.token.first()?.accessToken?.also {
                debug(2, it)
            }
        }

        val response = if (token == null) {
            chain.processWithoutToken(request)
        } else {
            chain.processWithToken(request, token)
        }

        if (response.code != CODE_FORBIDDEN) {
            return response
        }

        debug(3, response.toString())

        val newToken: String? = runBlocking(Dispatchers.IO) {
            mutex.withLock {
                val maybeUpdatedToken = authRepository.token.first()

                when {
                    maybeUpdatedToken == null -> null
                    maybeUpdatedToken.accessToken != token -> maybeUpdatedToken.accessToken
                    else -> {
                        authRepository.refreshToken()?.accessToken
                    }
                }
            }
        }.also {
            debug(4, it ?: "new token is null")
        }

        return if (newToken != null) chain.processWithToken(request, newToken) else response
    }

    private fun Interceptor.Chain.processWithToken(request: Request, token: String): Response {
        return request.newBuilder()
            .removeHeader(NetHeaders.KEY)
            .addHeader("token", token)
            .build()
            .let(this::proceed)
    }

    private fun Interceptor.Chain.processWithoutToken(request: Request): Response {
        return request.newBuilder()
            .removeHeader(NetHeaders.KEY)
            .build()
            .let(this::proceed)
    }

    private fun debug(num: Int, message: String) {
        Log.d("AuthInterceptor", "[$num]: $message")
    }
}