/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.di

import android.os.Build
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.runicgateway.app.BuildConfig
import com.runicgateway.app.core.net.AuthInterceptor
import com.runicgateway.app.core.net.BaseUrlHolder
import com.runicgateway.app.core.net.HostSelectionInterceptor
import com.runicgateway.app.core.net.ShardStream
import com.runicgateway.app.core.net.ShardStreamClient
import com.runicgateway.app.core.net.TokenAuthenticator
import com.runicgateway.app.core.net.UserAgentInterceptor
import com.runicgateway.app.data.api.AuthApi
import com.runicgateway.app.data.api.AuthRefreshApi
import com.runicgateway.app.data.api.MeApi
import com.runicgateway.app.data.api.AdminApi
import com.runicgateway.app.data.api.NotificationsApi
import com.runicgateway.app.data.api.PlayerShardApi
import com.runicgateway.app.data.api.PublicApi
import com.runicgateway.app.data.api.SsoApi
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 javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideJson(): Json = Json {
        // Additive backend fields (§8) must never break decoding; be lenient.
        ignoreUnknownKeys = true
        explicitNulls = false
        coerceInputValues = true
    }

    @Provides
    @Singleton
    fun provideUserAgentInterceptor(): UserAgentInterceptor {
        // Identifiable UA so the site's scanner guard (§8) doesn't reject the app.
        val ua = "RunicGatewayApp/${BuildConfig.VERSION_NAME} (Android ${Build.VERSION.RELEASE})"
        return UserAgentInterceptor(ua)
    }

    /**
     * The main client every API and the SSE stream ride on. Order: identify (UA),
     * retarget onto the configured shard host, then attach the bearer; the
     * [TokenAuthenticator] handles silent refresh-on-401 (§4.1). Logging sits last
     * so it observes the final, authed request.
     */
    @Provides
    @Singleton
    fun provideOkHttpClient(
        hostSelectionInterceptor: HostSelectionInterceptor,
        userAgentInterceptor: UserAgentInterceptor,
        authInterceptor: AuthInterceptor,
        tokenAuthenticator: TokenAuthenticator,
    ): OkHttpClient {
        val builder = OkHttpClient.Builder()
            .addInterceptor(userAgentInterceptor)
            .addInterceptor(hostSelectionInterceptor)
            .addInterceptor(authInterceptor)
            .authenticator(tokenAuthenticator)

        if (BuildConfig.DEBUG) {
            builder.addInterceptor(
                HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC },
            )
        }
        return builder.build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(client: OkHttpClient, json: Json): Retrofit =
        Retrofit.Builder()
            // Compile-time sentinel; every relative call is rewritten onto the
            // runtime-selected shard site by HostSelectionInterceptor (§3).
            .baseUrl(BaseUrlHolder.PLACEHOLDER_BASE_URL)
            .client(client)
            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
            .build()

    @Provides
    @Singleton
    fun providePublicApi(retrofit: Retrofit): PublicApi = retrofit.create(PublicApi::class.java)

    /** Expose the live SSE feed as the [ShardStream] capability so repositories depend
     *  on the interface (unit-testable against a fake), not the OkHttp-backed client. */
    @Provides
    @Singleton
    fun provideShardStream(client: ShardStreamClient): ShardStream = client

    @Provides
    @Singleton
    fun provideAuthApi(retrofit: Retrofit): AuthApi = retrofit.create(AuthApi::class.java)

    /** Native SSO discovery + code exchange (§4.2, M9) — on the main client. */
    @Provides
    @Singleton
    fun provideSsoApi(retrofit: Retrofit): SsoApi = retrofit.create(SsoApi::class.java)

    /** Role-agnostic self-service (§6.4) — bearer-authed on the main client. */
    @Provides
    @Singleton
    fun provideMeApi(retrofit: Retrofit): MeApi = retrofit.create(MeApi::class.java)

    /** A player's own game data + linking (§6.3) — bearer-authed on the main client. */
    @Provides
    @Singleton
    fun providePlayerShardApi(retrofit: Retrofit): PlayerShardApi =
        retrofit.create(PlayerShardApi::class.java)

    /** Opt-in push devices + subscriptions (§11, M7) — bearer-authed on the main client. */
    @Provides
    @Singleton
    fun provideNotificationsApi(retrofit: Retrofit): NotificationsApi =
        retrofit.create(NotificationsApi::class.java)

    /** Staff operations (§1, §6.4, M10) — bearer-authed; the server re-checks role every call. */
    @Provides
    @Singleton
    fun provideAdminApi(retrofit: Retrofit): AdminApi = retrofit.create(AdminApi::class.java)

    /**
     * Token refresh runs on its own **bare** client — UA + host retargeting only,
     * no auth interceptor and no authenticator — so a refresh can never recurse
     * back through [TokenAuthenticator] (§4.3). This throwaway client/Retrofit is
     * not exposed as a bean, so there is no ambiguous [OkHttpClient] binding.
     */
    @Provides
    @Singleton
    fun provideAuthRefreshApi(
        hostSelectionInterceptor: HostSelectionInterceptor,
        userAgentInterceptor: UserAgentInterceptor,
        json: Json,
    ): AuthRefreshApi {
        val bareClient = OkHttpClient.Builder()
            .addInterceptor(userAgentInterceptor)
            .addInterceptor(hostSelectionInterceptor)
            .build()
        val retrofit = Retrofit.Builder()
            .baseUrl(BaseUrlHolder.PLACEHOLDER_BASE_URL)
            .client(bareClient)
            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
            .build()
        return retrofit.create(AuthRefreshApi::class.java)
    }
}
