package cy.hexor.furumi.data.remote

import cy.hexor.furumi.data.local.AuthSessionStorage
import cy.hexor.furumi.data.remote.AccessTokenInterceptor.Companion.HEADER_AUTHORIZATION
import cy.hexor.furumi.data.remote.api.AuthApi
import cy.hexor.furumi.data.remote.model.RefreshRequest
import cy.hexor.furumi.data.remote.model.toDomain
import dagger.Lazy
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import javax.inject.Inject

class AuthAuthenticator @Inject constructor(
    private val sessionStorage: AuthSessionStorage,
    private val authEndpoints: AuthEndpoints,
    private val authApiLazy: Lazy<AuthApi>
) : Authenticator {

    private val refreshLock = Any()

    override fun authenticate(route: Route?, response: Response): Request? {
        if (responseCount(response) >= MAX_AUTH_ATTEMPTS ||
            authEndpoints.isLoginOrRefreshPath(response.request.url.encodedPath)
        ) {
            return null
        }

        val requestAuthorization = response.request.header(HEADER_AUTHORIZATION) ?: return null

        return synchronized(refreshLock) {
            val currentTokens = sessionStorage.getTokens() ?: return@synchronized null
            if (requestAuthorization != currentTokens.authorizationHeader) {
                return@synchronized response.withAuthorization(currentTokens.authorizationHeader)
            }

            val baseUrl = sessionStorage.getBaseUrl() ?: return@synchronized null
            val refreshedTokens = runBlocking {
                refreshTokens(baseUrl, currentTokens.refreshToken)
            }

            if (refreshedTokens == null) {
                sessionStorage.clearSession()
                null
            } else {
                sessionStorage.saveTokens(refreshedTokens)
                response.withAuthorization(refreshedTokens.authorizationHeader)
            }
        }
    }

    private suspend fun refreshTokens(
        baseUrl: String,
        refreshToken: String
    ): cy.hexor.furumi.domain.model.AuthTokens? {
        val refreshResponse = runCatching {
            authApiLazy.get().refresh(
                url = authEndpoints.refresh(baseUrl),
                request = RefreshRequest(refreshToken)
            )
        }.getOrNull()

        if (refreshResponse?.isSuccessful != true) {
            return null
        }

        val body = refreshResponse.body() ?: return null
        return body.toDomain(nowEpochSeconds())
    }

    private fun Response.withAuthorization(authorizationHeader: String): Request {
        return request.newBuilder()
            .header(HEADER_AUTHORIZATION, authorizationHeader)
            .build()
    }

    private fun responseCount(response: Response): Int {
        var count = 1
        var priorResponse = response.priorResponse
        while (priorResponse != null) {
            count++
            priorResponse = priorResponse.priorResponse
        }
        return count
    }

    private fun nowEpochSeconds(): Long = System.currentTimeMillis() / 1000L

    companion object {
        private const val MAX_AUTH_ATTEMPTS = 2
    }
}
