# NetworkModule.kt

    /*
     * The MIT License
     *
     * Original work sponsored and donated by The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     * Copyright (C) 2023 The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
     * of the Software, and to permit persons to whom the Software is furnished to do
     * so, subject to the following conditions:
     *
     *
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     *
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */
    package dk.nsp.mingraviditet.core.network.di

    import android.content.Context
    import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
    import dagger.Module
    import dagger.Provides
    import dagger.hilt.InstallIn
    import dagger.hilt.android.qualifiers.ApplicationContext
    import dagger.hilt.components.SingletonComponent
    import dk.nsp.mingraviditet.core.network.BuildConfig
    import dk.nsp.mingraviditet.core.network.appointments.AppointmentsOfflineDataSource
    import dk.nsp.mingraviditet.core.network.appointments.AssetManager
    import dk.nsp.mingraviditet.core.network.articles.ArticlesApi
    import dk.nsp.mingraviditet.core.network.articles.ArticlesDataSource
    import dk.nsp.mingraviditet.core.network.articles.ArticlesV2Api
    import dk.nsp.mingraviditet.core.network.authenticator.Authenticator
    import dk.nsp.mingraviditet.core.network.backgroundInfo.BackgroundInfoApi
    import dk.nsp.mingraviditet.core.network.backgroundInfo.BackgroundInfoDataSource
    import dk.nsp.mingraviditet.core.network.choosePregnancy.DueDatesApi
    import dk.nsp.mingraviditet.core.network.cms.CMSApi
    import dk.nsp.mingraviditet.core.network.cms.CMSDataSource
    import dk.nsp.mingraviditet.core.network.earlierVisits.EarlierVisitsApi
    import dk.nsp.mingraviditet.core.network.earlierVisits.EarlierVisitsDataSource
    import dk.nsp.mingraviditet.core.network.faq.FaqApi
    import dk.nsp.mingraviditet.core.network.faq.FaqDataSource
    import dk.nsp.mingraviditet.core.network.healthcareContacts.HealthcareContactsApi
    import dk.nsp.mingraviditet.core.network.healthcareContacts.HealthcareContactsDataSource
    import dk.nsp.mingraviditet.core.network.interceptor.AccessTokenInterceptor
    import dk.nsp.mingraviditet.core.network.measurements.MeasurementsApi
    import dk.nsp.mingraviditet.core.network.measurements.MeasurementsDataSource
    import dk.nsp.mingraviditet.core.network.pregnancyInfo.AuthorizedPregnancyInfoApi
    import dk.nsp.mingraviditet.core.network.pregnancyInfo.PregnancyInfoDataSource
    import dk.nsp.mingraviditet.core.network.pregnancyInfo.UnauthorizedPregnancyInfoApi
    import dk.nsp.mingraviditet.core.network.util.converters.LocalDateSerializer
    import dk.nsp.mingraviditet.core.network.util.converters.LocalDateTimeSerializer
    import dk.nsp.mingraviditet.core.shared.authentication.IAuthController
    import dk.nsp.mingraviditet.core.shared.network.Dispatcher
    import dk.nsp.mingraviditet.core.shared.network.MGDispatchers
    import kotlinx.coroutines.CoroutineDispatcher
    import kotlinx.serialization.json.Json
    import kotlinx.serialization.modules.SerializersModule
    import okhttp3.Call
    import okhttp3.ConnectionSpec
    import okhttp3.MediaType.Companion.toMediaType
    import okhttp3.OkHttpClient
    import okhttp3.logging.HttpLoggingInterceptor
    import retrofit2.Retrofit
    import java.time.LocalDate
    import java.time.LocalDateTime
    import java.util.concurrent.TimeUnit
    import javax.inject.Qualifier
    import javax.inject.Singleton

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

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class AuthenticatedClient

        @AuthenticatedClient
        @Provides
        @Singleton
        fun provideAuthenticatedOkHttpCallFactory(
            authController: IAuthController,
            authenticator: Authenticator,
        ): Call.Factory = OkHttpClient.Builder()
            .authenticator(authenticator)
            .addInterceptor(
                AccessTokenInterceptor(
                    authController,
                ),
            )
            .connectTimeout(30, TimeUnit.MINUTES)
            .writeTimeout(30, TimeUnit.MINUTES)
            .readTimeout(100, TimeUnit.SECONDS)
            .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS))
            .addNetworkInterceptor(
                HttpLoggingInterceptor().apply {
                    if (BuildConfig.AllowLogNetwork) {
                        level = HttpLoggingInterceptor.Level.BASIC
                    }
                },
            )
            .build()

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class UnAuthenticatedClient

        @UnAuthenticatedClient
        @Provides
        @Singleton
        fun provideUnauthenticatedOkHttpCallFactory(): Call.Factory = OkHttpClient.Builder()
            .connectTimeout(1, TimeUnit.MINUTES)
            .writeTimeout(1, TimeUnit.MINUTES)
            .readTimeout(30, TimeUnit.SECONDS)
            .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS))
            .addNetworkInterceptor(
                HttpLoggingInterceptor().apply {
                    if (BuildConfig.AllowLogNetwork) {
                        level = HttpLoggingInterceptor.Level.BASIC
                    }
                },
            )
            .build()

        @Provides
        @Singleton
        fun provideAuthenticator(
            authController: IAuthController,
        ): Authenticator = Authenticator(
            authController = authController,
        )

        @Provides
        @Singleton
        fun provideNetworkJson(): Json = Json {
            ignoreUnknownKeys = true
            explicitNulls = false
            serializersModule = SerializersModule {
                contextual(LocalDateTime::class, LocalDateTimeSerializer)
                contextual(LocalDate::class, LocalDateSerializer)
            }
        }

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class CMSRetrofit

        @Provides
        @Singleton
        @CMSRetrofit
        fun provideRetrofitForCMS(
            networkJson: Json,
            @UnAuthenticatedClient okHttpCallFactory: Call.Factory,
        ): Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.cmsBackendUrl)
            .callFactory(okHttpCallFactory)
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType()),
            )
            .build()

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class CMSRetrofitV2

        @Provides
        @Singleton
        @CMSRetrofitV2
        fun provideRetrofitForCMSV2(
            networkJson: Json,
            @UnAuthenticatedClient okHttpCallFactory: Call.Factory,
        ): Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.cmsBackendUrlV2)
            .callFactory(okHttpCallFactory)
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType()),
            )
            .build()

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class UnauthenticatedBFFRetrofit

        @Provides
        @Singleton
        @UnauthenticatedBFFRetrofit
        fun provideUnauthenticatedRetrofitForBFF(
            networkJson: Json,
            @UnAuthenticatedClient okHttpCallFactory: Call.Factory,
        ): Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.unauthorizedBffBackendUrl)
            .callFactory(okHttpCallFactory)
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType()),
            )
            .build()

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class AuthenticatedBFFRetrofit

        @Provides
        @Singleton
        @AuthenticatedBFFRetrofit
        fun provideRetrofitForBFF(
            networkJson: Json,
            @AuthenticatedClient okHttpCallFactory: Call.Factory,
        ): Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.bffBackendUrl)
            .callFactory(okHttpCallFactory)
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType()),
            )
            .build()

        @Qualifier
        @Retention(AnnotationRetention.RUNTIME)
        annotation class AuthenticatedBFFRetrofitV2

        @Provides
        @Singleton
        @AuthenticatedBFFRetrofitV2
        fun provideRetrofitForBFFV2(
            networkJson: Json,
            @AuthenticatedClient okHttpCallFactory: Call.Factory,
        ): Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.bffBackendUrlV2)
            .callFactory(okHttpCallFactory)
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType()),
            )
            .build()

        @Provides
        @Singleton
        fun provideCMSDataSource(
            api: CMSApi,
        ): CMSDataSource = CMSDataSource(api)

        @Provides
        @Singleton
        fun provideCMSApi(
            @CMSRetrofit retrofit: Retrofit,
        ): CMSApi = retrofit.create(CMSApi::class.java)

        @Provides
        @Singleton
        fun providesLocalAssetsManager(
            @ApplicationContext context: Context,
        ): AssetManager = AssetManager(context.assets::open)

        @Provides
        @Singleton
        fun providesCareplanDataSource(
            json: Json,
            assets: AssetManager,
            @Dispatcher(MGDispatchers.IO) ioDispatcher: CoroutineDispatcher,
        ) = AppointmentsOfflineDataSource(
            json = json,
            assets = assets,
            ioDispatcher = ioDispatcher,
        )

        @Provides
        @Singleton
        fun provideAuthorizedPregnancyInfoApi(
            @AuthenticatedBFFRetrofit retrofit: Retrofit,
        ): AuthorizedPregnancyInfoApi = retrofit.create(AuthorizedPregnancyInfoApi::class.java)

        @Provides
        @Singleton
        fun provideUnAuthorizedPregnancyInfoApi(
            @UnauthenticatedBFFRetrofit retrofit: Retrofit,
        ): UnauthorizedPregnancyInfoApi = retrofit.create(UnauthorizedPregnancyInfoApi::class.java)

        @Provides
        @Singleton
        fun providePregnancyInfoDataStore(
            authorizedApi: AuthorizedPregnancyInfoApi,
            unauthorizedApi: UnauthorizedPregnancyInfoApi,
        ) = PregnancyInfoDataSource(
            authorizedApi,
            unauthorizedApi,
        )

        @Provides
        @Singleton
        fun provideDueDatesApi(
            @AuthenticatedBFFRetrofitV2 retrofit: Retrofit,
        ): DueDatesApi = retrofit.create(DueDatesApi::class.java)

        @Provides
        @Singleton
        fun provideArticlesApi(
            @CMSRetrofitV2 retrofit: Retrofit,
        ): ArticlesV2Api = retrofit.create(ArticlesV2Api::class.java)

        @Provides
        @Singleton
        fun provideCategoriesApi(
            @CMSRetrofit retrofit: Retrofit,
        ): ArticlesApi = retrofit.create(ArticlesApi::class.java)

        @Provides
        @Singleton
        fun provideArticlesDataStore(
            articlesApi: ArticlesApi,
            articlesV2Api: ArticlesV2Api,
        ) = ArticlesDataSource(
            articlesApi = articlesApi,
            articlesV2Api = articlesV2Api,
        )

        @Provides
        @Singleton
        fun provideFaqApi(
            @CMSRetrofit retrofit: Retrofit,
        ): FaqApi = retrofit.create(FaqApi::class.java)

        @Provides
        @Singleton
        fun provideFaqDataSource(
            api: FaqApi,
            @Dispatcher(MGDispatchers.IO) ioDispatcher: CoroutineDispatcher,
        ) = FaqDataSource(
            api,
            ioDispatcher,
        )

        @Provides
        @Singleton
        fun provideMeasurementsApi(
            @AuthenticatedBFFRetrofit retrofit: Retrofit,
        ): MeasurementsApi = retrofit.create(MeasurementsApi::class.java)

        @Provides
        @Singleton
        fun provideMeasurementsDataSource(
            api: MeasurementsApi,
        ) = MeasurementsDataSource(
            api,
        )

        @Provides
        @Singleton
        fun provideBackgroundInfoApi(
            @AuthenticatedBFFRetrofit retrofit: Retrofit,
        ): BackgroundInfoApi = retrofit.create(BackgroundInfoApi::class.java)

        @Provides
        @Singleton
        fun provideBackgroundInfoDataSource(
            api: BackgroundInfoApi,
        ) = BackgroundInfoDataSource(
            api,
        )

        @Provides
        @Singleton
        fun provideHealthcareContactsDataSource(
            api: HealthcareContactsApi,
        ) = HealthcareContactsDataSource(
            api,
        )

        @Provides
        @Singleton
        fun provideHealthcareContactsApi(
            @AuthenticatedBFFRetrofit retrofit: Retrofit,
        ): HealthcareContactsApi = retrofit.create(HealthcareContactsApi::class.java)

        @Provides
        @Singleton
        fun provideEarlierVisitsApi(
            @AuthenticatedBFFRetrofit retrofit: Retrofit,
        ): EarlierVisitsApi = retrofit.create(EarlierVisitsApi::class.java)

        @Provides
        @Singleton
        fun provideEarlierVisitsDataSource(
            api: EarlierVisitsApi,
        ) = EarlierVisitsDataSource(
            api,
        )
    }