package com.fabledsword.minstrel.api

import com.fabledsword.minstrel.BuildConfig
import com.fabledsword.minstrel.connectivity.ReachabilityReportingInterceptor
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

/**
 * Provides a single shared OkHttp client + Retrofit instance for the
 * whole app. Per-endpoint Retrofit interfaces (LibraryApi, PlaylistsApi,
 * etc.) are provided by feature modules using
 * `retrofit.create(EndpointApi::class.java)` in the relevant @Provides.
 *
 * Streaming audio HTTP (ExoPlayer) reuses this same OkHttp via
 * `OkHttpDataSource.Factory` so the auth interceptor, connection pool,
 * and TLS config are shared.
 */
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideHttpLogging(): HttpLoggingInterceptor =
        HttpLoggingInterceptor().apply {
            level =
                if (BuildConfig.DEBUG) {
                    HttpLoggingInterceptor.Level.BASIC
                } else {
                    HttpLoggingInterceptor.Level.NONE
                }
        }

    @Provides
    @Singleton
    fun provideOkHttp(
        baseUrl: BaseUrlInterceptor,
        auth: AuthCookieInterceptor,
        reachability: ReachabilityReportingInterceptor,
        logging: HttpLoggingInterceptor,
    ): OkHttpClient =
        OkHttpClient.Builder()
            // ReachabilityReportingInterceptor MUST run first: it identifies
            // Minstrel-bound requests by the still-unrewritten PLACEHOLDER_HOST
            // (so external artwork fetches don't read as server reachability)
            // and observes the final transport outcome by wrapping the chain.
            .addInterceptor(reachability)
            // AuthCookieInterceptor MUST run before BaseUrlInterceptor.
            // Both scope on `host == PLACEHOLDER_HOST` to distinguish
            // Minstrel-server requests from external image fetches
            // (drift #568 / #569). If BaseUrlInterceptor runs first it
            // rewrites the host to the real server before auth sees the
            // request, auth's placeholder check fails, and the session
            // cookie is neither attached on outgoing requests nor
            // captured from Set-Cookie on login — fresh installs get
            // stuck at the Welcome screen. Auth first means it sees
            // placeholder.invalid, attaches/captures correctly, then
            // BaseUrlInterceptor retargets to the live AuthStore host
            // for transport.
            .addInterceptor(auth)
            .addInterceptor(baseUrl)
            .addInterceptor(logging)
            .connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
            .readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
            .build()

    @Provides
    @Singleton
    fun provideRetrofit(
        okHttp: OkHttpClient,
        json: Json,
    ): Retrofit =
        Retrofit.Builder()
            // Placeholder base URL — BaseUrlInterceptor rewrites every
            // outgoing request to the live AuthStore value, so this
            // string only has to satisfy Retrofit's parser and is
            // never reached as an actual host. This lets the user
            // change server URL in Settings without an app relaunch.
            .baseUrl("http://placeholder.invalid/")
            .client(okHttp)
            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
            .build()

    // Per-endpoint Retrofit interface @Provides intentionally NOT here.
    // Feature repositories construct their interface via the shared
    // Retrofit (Hilt-injected), e.g.:
    //
    //   class LibraryRepository @Inject constructor(retrofit: Retrofit) {
    //       private val api = retrofit.create<LibraryApi>()
    //       ...
    //   }
    //
    // Fewer Hilt bindings + no KSP2 type-resolution sharp edges from
    // Hilt processing a `@Provides` return type that's a hand-written
    // Kotlin interface (Hilt's ModuleProcessingStep "could not be
    // resolved" failure mode under KSP2 — google/dagger#4303).

    private const val CONNECT_TIMEOUT_SECONDS = 10L
    private const val READ_TIMEOUT_SECONDS = 30L
}
