# ArticlesRepository.kt

    /*
     * The MIT License
     *
     * Original work sponsored and donated by The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     * Copyright (C) 2021-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.data.repository

    import dk.nsp.mingraviditet.core.data.domain.articles.OverviewArticles
    import dk.nsp.mingraviditet.core.data.models.toEntity
    import dk.nsp.mingraviditet.core.data.util.searchResults
    import dk.nsp.mingraviditet.core.database.dao.ArticleCategoryDao
    import dk.nsp.mingraviditet.core.database.dao.ArticleDao
    import dk.nsp.mingraviditet.core.database.dao.ArticleSectionDao
    import dk.nsp.mingraviditet.core.database.model.ArticleCategoryWithArticleCountEntity
    import dk.nsp.mingraviditet.core.database.model.asExternalModel
    import dk.nsp.mingraviditet.core.model.data.Article
    import dk.nsp.mingraviditet.core.model.data.ArticleCategory
    import dk.nsp.mingraviditet.core.model.data.SearchResult
    import dk.nsp.mingraviditet.core.network.cms.CMSDataSource
    import dk.nsp.mingraviditet.core.network.cms.models.NetworkRecommendedArticles
    import dk.nsp.mingraviditet.core.network.cms.models.asExternalModel
    import dk.nsp.mingraviditet.core.shared.network.Dispatcher
    import dk.nsp.mingraviditet.core.shared.network.Failed
    import dk.nsp.mingraviditet.core.shared.network.MGDispatchers
    import dk.nsp.mingraviditet.core.shared.network.ResultWrapper
    import dk.nsp.mingraviditet.core.shared.network.Success
    import dk.nsp.mingraviditet.core.shared.network.asSuccessValueOrNull
    import dk.nsp.mingraviditet.core.shared.network.mapSuccessWithTryOrFailed
    import kotlinx.coroutines.CoroutineDispatcher
    import kotlinx.coroutines.flow.Flow
    import kotlinx.coroutines.flow.flowOn
    import kotlinx.coroutines.flow.map
    import kotlinx.coroutines.withContext
    import timber.log.Timber
    import javax.inject.Inject
    import javax.inject.Singleton

    @Singleton
    class ArticlesRepository @Inject constructor(
        private val cmsDataSource: CMSDataSource,
        private val articleCategoriesDao: ArticleCategoryDao,
        private val articlesDao: ArticleDao,
        private val articleSectionDao: ArticleSectionDao,
        @Dispatcher(MGDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
    ) {

        suspend fun getOverviewArticles(week: Int?): ResultWrapper<OverviewArticles> = withContext(ioDispatcher) {
            getAllArticlesAndInsertIntoDatabase()

            if (week != null) {
                cmsDataSource.getArticleCategories(week).mapToOverviewArticles()
            } else {
                val randomWeek = (1..10).random()
                cmsDataSource.getArticleCategories(randomWeek).mapToOverviewArticles(includeNextWeek = false)
            }
        }

        private fun ResultWrapper<NetworkRecommendedArticles>.mapToOverviewArticles(includeNextWeek: Boolean = true): ResultWrapper<OverviewArticles> =
            mapSuccessWithTryOrFailed {
                OverviewArticles(
                    currentWeek = it.currentWeek.flatMap { it.asExternalModel().searchResults() },
                    nextWeek = if (includeNextWeek) it.nextWeek?.flatMap { it.asExternalModel().searchResults() } else null,
                )
            }

        suspend fun getAfterBirthArticles(afterBirthWeek: Int): ResultWrapper<OverviewArticles> = withContext(ioDispatcher) {
            when (val res = cmsDataSource.getAfterBirthArticleCategories(afterBirthWeek.coerceAtMost(8))) {
                is Failed -> res
                is Success -> res.mapToOverviewArticles()
            }
        }

        suspend fun getAllArticlesAndInsertIntoDatabase() = withContext(ioDispatcher) {
            cmsDataSource.getArticleSections().asSuccessValueOrNull()?.forEachIndexed { index, category ->
                articleCategoriesDao.insertArticleCategory(category.toEntity(index + 1)) // Adding 1 to index to start with 1-based order
                category.articles.forEachIndexed { articleIndex, article ->
                    articlesDao.insertArticle(article = article.toEntity(category.categoryId, index = articleIndex + 1))
                    article.sections.forEachIndexed { sectionIndex, section ->
                        articleSectionDao.insertSection(section.toEntity(articleId = article.id, index = sectionIndex + 1))
                    }
                }
            }
        }

        val articleCategoriesWithCount = articleCategoriesDao.getArticleCategoriesWithArticleCount().map {
            it.map(ArticleCategoryWithArticleCountEntity::asExternalModel).sortedBy { it.category.order }
        }.flowOn(ioDispatcher)

        suspend fun getPopulatedArticle(articleId: Int): Article = withContext(ioDispatcher) {
            articlesDao.getPopulatedArticle(articleId).asExternalModel()
        }

        fun getPopulatedCategory(categoryId: Int): Flow<ArticleCategory> {
            return articleCategoriesDao.getPopulatedCategory(categoryId).map { it.asExternalModel() }.flowOn(ioDispatcher)
        }

        suspend fun search(
            query: String,
        ): ResultWrapper<List<SearchResult>> = withContext(ioDispatcher) {
            val searchResponse = cmsDataSource.searchArticles(query)

            searchResponse.mapSuccessWithTryOrFailed { results ->
                results.map { it.asExternalModel() }
            }
        }

        suspend fun getInternalArticleByLink(internalLink: String): Pair<Article?, Int> = withContext(ioDispatcher) {
            val redirectLocation = cmsDataSource.getRedirectLink(internalLink)
            val articleId = redirectLocation.substringAfterLast("/").toIntOrNull() ?: return@withContext Pair(null, -1)

            when (val redirect = cmsDataSource.getRedirectArticle(articleId)) {
                is Failed -> {
                    Timber.e("Couldn't find linked article", redirect.error)
                    Pair(null, -1)
                }

                is Success -> {
                    Pair(redirect.value.article.asExternalModel(), redirect.value.sectionId ?: -1)
                }
            }
        }
    }