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

import androidx.lifecycle.SavedStateHandle
import com.runicgateway.app.data.api.dto.PageDto
import com.runicgateway.app.data.api.dto.PostDto
import com.runicgateway.app.data.api.dto.StatusDto
import com.runicgateway.app.data.api.dto.WikiPageDto
import com.runicgateway.app.data.api.dto.WikiSummaryDto
import com.runicgateway.app.data.api.fake.FakePublicApi
import com.runicgateway.app.data.repository.ContentRepository
import com.runicgateway.app.data.repository.SettingsRepository
import com.runicgateway.app.data.repository.WikiRepository
import com.runicgateway.app.ui.home.HomeViewModel
import com.runicgateway.app.ui.navigation.Routes
import com.runicgateway.app.ui.news.NewsViewModel
import com.runicgateway.app.ui.news.PostViewModel
import com.runicgateway.app.ui.page.PageViewModel
import com.runicgateway.app.ui.wiki.WikiPageViewModel
import com.runicgateway.app.ui.wiki.WikiViewModel
import com.runicgateway.app.util.MainDispatcherRule
import com.runicgateway.app.util.httpError
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test

/** ViewModels over the public content APIs (news, CMS pages, wiki, home status). */
class ContentViewModelTest {

    @get:Rule val mainDispatcher = MainDispatcherRule()

    private val api = FakePublicApi()
    private val content = ContentRepository(api)
    private val wiki = WikiRepository(api)
    private val settings = SettingsRepository(api)

    // ── News hub ──────────────────────────────────────────────────────────
    /** No category argument: how every route into the hub but §6.2's arrives. */
    private fun newsViewModel(category: String? = null) =
        NewsViewModel(
            content,
            SavedStateHandle(category?.let { mapOf(Routes.Args.CATEGORY to it) } ?: emptyMap()),
        )

    @Test fun newsLoadsSelectedCategory() {
        api.posts = listOf(PostDto(id = 1, category = "news", title = "Hi"))
        val vm = newsViewModel()
        assertTrue(vm.state.value is UiState.Success)
        assertEquals(1, (vm.state.value as UiState.Success).data.size)
    }

    @Test fun newsSelectCategoryReloads() {
        val vm = newsViewModel()
        api.posts = listOf(PostDto(id = 2, category = "newsletter", title = "N"))
        vm.selectCategory(ContentRepository.PostCategory.NEWSLETTER)
        assertEquals(ContentRepository.PostCategory.NEWSLETTER, vm.category.value)
        assertEquals(1, (vm.state.value as UiState.Success).data.size)
    }

    @Test fun newsServerErrorIsUiError() {
        api.error = httpError(500)
        assertTrue(newsViewModel().state.value is UiState.Error)
    }

    @Test fun newsOpensOnTheCategoryTheRouteAsksFor() {
        // The app's half of an admin's nav override or added link pointing at one of
        // the website's three category pages (THEMING_AND_NAV.md §6.2).
        val vm = newsViewModel("five-on-friday")
        assertEquals(ContentRepository.PostCategory.FIVE_ON_FRIDAY, vm.category.value)
    }

    @Test fun newsFallsBackToTheDefaultFeedForAnUnknownCategory() {
        // A hand-edited settings row, or a category the site has and the app doesn't.
        assertEquals(ContentRepository.PostCategory.NEWS, newsViewModel("bogus").category.value)
        assertEquals(ContentRepository.PostCategory.NEWS, newsViewModel().category.value)
    }

    // ── Post detail (SavedStateHandle args) ─────────────────────────────────
    @Test fun postDetailLoadsForKnownCategory() {
        api.post = PostDto(id = 7, category = "news", title = "Update", body = "<p>x</p>")
        val handle = SavedStateHandle(
            mapOf(Routes.Args.CATEGORY to "news", Routes.Args.ID_OR_SLUG to "update"),
        )
        val vm = PostViewModel(content, handle)
        assertEquals("Update", (vm.state.value as UiState.Success).data.title)
    }

    @Test fun postDetailUnknownCategoryIsNotFoundWithoutApiCall() {
        val handle = SavedStateHandle(
            mapOf(Routes.Args.CATEGORY to "bogus", Routes.Args.ID_OR_SLUG to "x"),
        )
        val state = PostViewModel(content, handle).state.value
        assertTrue(state is UiState.Error)
        assertEquals(ErrorKind.NOT_FOUND, (state as UiState.Error).kind)
    }

    // ── CMS page ────────────────────────────────────────────────────────────
    @Test fun pageLoadsBySlug() {
        api.page = PageDto(id = 3, slug = "about", title = "About")
        val vm = PageViewModel(content, SavedStateHandle(mapOf(Routes.Args.SLUG to "about")))
        assertEquals("About", (vm.state.value as UiState.Success).data.title)
    }

    @Test fun pageNotFoundIsUiError() {
        api.error = httpError(404)
        val vm = PageViewModel(content, SavedStateHandle(mapOf(Routes.Args.SLUG to "missing")))
        assertEquals(ErrorKind.NOT_FOUND, (vm.state.value as UiState.Error).kind)
    }

    // ── Wiki index + detail ─────────────────────────────────────────────────
    @Test fun wikiIndexLoadsAndTracksQuery() {
        api.wikiPages = listOf(WikiSummaryDto(id = 1, slug = "pvp", title = "PvP"))
        val vm = WikiViewModel(wiki)
        assertTrue(vm.state.value is UiState.Success)
        vm.onQueryChange("housing")
        assertEquals("housing", vm.query.value)
    }

    @Test fun wikiPageLoadsBySlug() {
        api.wikiPage = WikiPageDto(id = 9, slug = "housing", title = "Housing", body = "b")
        val vm = WikiPageViewModel(wiki, SavedStateHandle(mapOf(Routes.Args.SLUG to "housing")))
        assertEquals("Housing", (vm.state.value as UiState.Success).data.title)
    }

    // ── Home status ─────────────────────────────────────────────────────────
    @Test fun homeLoadsStatus() {
        api.status = StatusDto(mode = "maintenance")
        val vm = HomeViewModel(settings)
        assertTrue((vm.state.value as UiState.Success).data.isMaintenance)
    }

    @Test fun homeNetworkErrorIsUiError() {
        api.error = java.io.IOException("offline")
        val state = HomeViewModel(settings).state.value
        assertEquals(ErrorKind.NETWORK, (state as UiState.Error).kind)
    }
}
