package com.anypreta.bountyboard.data

import android.content.Context
import coil.ImageLoader
import com.anypreta.bountyboard.data.net.ApiService
import com.anypreta.bountyboard.data.net.CsrfInterceptor
import com.anypreta.bountyboard.data.net.HostSelectionInterceptor
import com.anypreta.bountyboard.data.net.PersistentCookieJar
import com.google.gson.Gson
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

/**
 * Manual dependency container — created once in [com.anypreta.bountyboard.BountyBoardApp].
 * Holds the network stack, repository, session, and settings. The base URL is dynamic:
 * [setBaseUrl] retargets every request without rebuilding Retrofit.
 */
class AppContainer(
    private val appContext: Context,
    initialBaseUrl: String,
) {
    val settings = SettingsStore(appContext)
    val session = SessionManager()
    val gson: Gson = Gson()

    val cookieJar = PersistentCookieJar(appContext)
    private val hostInterceptor = HostSelectionInterceptor(initialBaseUrl)

    private val _baseUrl = MutableStateFlow(HostSelectionInterceptor.normalize(initialBaseUrl))
    val baseUrl: StateFlow<String> = _baseUrl.asStateFlow()

    private val okHttp: OkHttpClient = OkHttpClient.Builder()
        .cookieJar(cookieJar)
        .addInterceptor(hostInterceptor)
        .addInterceptor(CsrfInterceptor(cookieJar))
        .addInterceptor(
            HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC },
        )
        .connectTimeout(20, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .build()

    private val retrofit: Retrofit = Retrofit.Builder()
        // Placeholder; the host interceptor rewrites scheme/host/port per request.
        .baseUrl("http://localhost/")
        .client(okHttp)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()

    val api: ApiService = retrofit.create(ApiService::class.java)
    val repo = Repository(api, gson)

    /** Coil loader sharing the OkHttp stack so /files/{id} fetches carry cookies. */
    val imageLoader: ImageLoader = ImageLoader.Builder(appContext)
        .okHttpClient(okHttp)
        .crossfade(true)
        .build()

    /** The live-update WebSocket (chat + notifications + board). */
    val live = com.anypreta.bountyboard.data.ws.LiveSocket(okHttp, gson) { _baseUrl.value }

    /** Whether an Activity is currently in the foreground (drives background notifications). */
    @Volatile
    var appForeground: Boolean = false

    /** A pending in-app navigation target (set when the user taps a system notification). */
    val pendingDeepLink = MutableStateFlow<String?>(null)

    fun setDeepLink(route: String?) {
        if (!route.isNullOrBlank()) pendingDeepLink.value = route
    }

    fun consumeDeepLink() {
        pendingDeepLink.value = null
    }

    fun schedulePush() = com.anypreta.bountyboard.data.push.PushScheduler.schedule(appContext)

    fun cancelPush() = com.anypreta.bountyboard.data.push.PushScheduler.cancel(appContext)

    /** Start/stop the foreground service that keeps the socket alive when the app is closed. */
    fun startLiveService() = com.anypreta.bountyboard.data.push.LiveSocketService.start(appContext)

    fun stopLiveService() = com.anypreta.bountyboard.data.push.LiveSocketService.stop(appContext)

    suspend fun clearPushState() = com.anypreta.bountyboard.data.push.PushStateStore(appContext).clear()

    fun setBaseUrl(url: String) {
        val normalized = HostSelectionInterceptor.normalize(url)
        hostInterceptor.setBaseUrl(normalized)
        _baseUrl.value = normalized
    }

    /** Absolute URL for a stored file/avatar id. */
    fun fileUrl(fileId: String): String = _baseUrl.value.trimEnd('/') + "/files/" + fileId

    fun currentHost(): String? = hostInterceptor.currentHost()
}
