package com.github.nullptroma.wallenc.domain.vault.network.yandexdisk

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.nullptroma.wallenc.domain.vault.network.yandexdisk.repository.YandexDiskRepository
import com.github.nullptroma.wallenc.domain.vault.ports.YandexAccountStore
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit

/**
 * Фабрика REST-клиента Яндекс.Диска: отдельный [OkHttpClient] с OAuth на каждый vault,
 * плюс «голый» клиент для PUT/GET по одноразовым upload/download URL.
 */
class YandexDiskApiFactory(
    private val accountRepository: YandexAccountStore,
    private val ioDispatcher: CoroutineDispatcher,
) {

    /** Кеш OAuth-токена по vault, чтобы не дергать БД на каждый HTTP-запрос к cloud-api. */
    private val oauthTokenCache = ConcurrentHashMap<String, Pair<Long, String>>()

    private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }

    /** Без авторизации — только для одноразовых ссылок upload/download. */
    val rawHttpClient: OkHttpClient by lazy {
        newHttpClientBuilder().build()
    }

    /**
     * [tokenProvider] вызывается на каждый HTTP-запрос к cloud-api (свежий токен из БД).
     */
    fun createAuthenticatedApi(tokenProvider: () -> String?): YandexDiskApi {
        val client = newHttpClientBuilder()
            .addInterceptor { chain ->
                val token = tokenProvider()
                    ?: throw java.io.IOException("Yandex OAuth token is missing")
                val req = chain.request().newBuilder()
                    .header("Authorization", "OAuth $token")
                    .header("Accept", "application/json")
                    .build()
                chain.proceed(req)
            }
            .build()
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(client)
            .addConverterFactory(JacksonConverterFactory.create(jackson))
            .build()
            .create(YandexDiskApi::class.java)
    }

    /**
     * OAuth-токен загружается один раз при создании API (не в OkHttp interceptor).
     * При 401 см. [YandexDiskAuthException] и повторную привязку vault.
     */
    fun createApiForVault(vaultUuid: UUID): YandexDiskApi {
        val id = vaultUuid.toString()
        val token = runBlocking(ioDispatcher) {
            accountRepository.getByVaultUuid(id)?.oauthToken
        } ?: throw java.io.IOException("Yandex OAuth token is missing")
        oauthTokenCache[id] = System.currentTimeMillis() to token
        return createAuthenticatedApi {
            oauthTokenCache[id]?.second ?: token
        }
    }

    fun invalidateTokenCache(vaultUuid: UUID) {
        oauthTokenCache.remove(vaultUuid.toString())
    }

    companion object {
        const val BASE_URL = "https://cloud-api.yandex.net/"

        private const val CONNECT_TIMEOUT_SEC = 30L
        private const val READ_TIMEOUT_SEC = 120L
        private const val WRITE_TIMEOUT_SEC = 120L

        fun newHttpClientBuilder(): OkHttpClient.Builder =
            OkHttpClient.Builder()
                .connectTimeout(CONNECT_TIMEOUT_SEC, TimeUnit.SECONDS)
                .readTimeout(READ_TIMEOUT_SEC, TimeUnit.SECONDS)
                .writeTimeout(WRITE_TIMEOUT_SEC, TimeUnit.SECONDS)
        fun createRepositoryWithToken(
            oauthToken: String,
            ioDispatcher: CoroutineDispatcher,
        ): YandexDiskRepository {
            val client = newHttpClientBuilder()
                .addInterceptor { chain ->
                    chain.proceed(
                        chain.request().newBuilder()
                            .header("Authorization", "OAuth $oauthToken")
                            .header("Accept", "application/json")
                            .build(),
                    )
                }
                .build()
            val api = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(JacksonConverterFactory.create(jacksonObjectMapper().findAndRegisterModules()))
                .build()
                .create(YandexDiskApi::class.java)
            return YandexDiskRepository(api, OkHttpClient(), ioDispatcher)
        }
    }
}
