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

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * Decode/encode tests for the staff-operations DTOs (`/admin/…`, PLAN.md §6.4).
 * Covers the snake_case `@SerialName` mappings, the `AdminPostDto.isPublished`
 * tinyint bridge, nested dashboard shapes, and the request bodies the app encodes.
 */
class AdminDtoTest {

    private val json = Json {
        ignoreUnknownKeys = true
        explicitNulls = false
        coerceInputValues = true
    }

    @Test fun dashboardDecodesNestedCountsAndActivity() {
        val dto = json.decodeFromString<AdminDashboardDto>(
            """{
                "site_mode":"maintenance",
                "last_change":{"at":"2026-07-20T10:00:00Z","by":"admin"},
                "counts":{"posts":{"news":4,"newsletter":1},"users":37},
                "recent_activity":[
                    {"id":9,"username":"mod","action":"post.publish",
                     "detail":{"postId":12},"created_at":"2026-07-22T09:00:00Z"}
                ]
            }""",
        )
        assertEquals("maintenance", dto.siteMode)
        assertEquals("admin", dto.lastChange.by)
        assertEquals(4, dto.counts.posts["news"])
        assertEquals(37, dto.counts.users)
        assertEquals(1, dto.recentActivity.size)
        assertEquals("post.publish", dto.recentActivity[0].action)
        // `detail` is provider-shaped JSON kept as a raw element.
        assertEquals(12, dto.recentActivity[0].detail!!.jsonObject["postId"]!!.jsonPrimitive.int)
    }

    @Test fun dashboardDefaultsWhenKeysAbsent() {
        val dto = json.decodeFromString<AdminDashboardDto>("{}")
        assertEquals("live", dto.siteMode)
        assertTrue(dto.counts.posts.isEmpty())
        assertTrue(dto.recentActivity.isEmpty())
    }

    @Test fun adminPostBridgesPublishedTinyintToBoolean() {
        val published = json.decodeFromString<AdminPostDto>(
            """{"id":1,"category":"news","title":"Hi","published":1,"published_at":"2026-07-21T00:00:00Z"}""",
        )
        assertTrue(published.isPublished)
        assertEquals("2026-07-21T00:00:00Z", published.publishedAt)

        val draft = json.decodeFromString<AdminPostDto>("""{"id":2,"title":"Draft","published":0}""")
        assertFalse(draft.isPublished)
    }

    @Test fun adminWikiCategoryAndTagDecodeCounts() {
        val cat = json.decodeFromString<AdminWikiCategoryDto>(
            """{"id":3,"slug":"lore","title":"Lore","sort_order":2,"page_count":5,"published_count":4}""",
        )
        assertEquals(2, cat.sortOrder)
        assertEquals(5, cat.pageCount)
        assertEquals(4, cat.publishedCount)

        val tag = json.decodeFromString<AdminWikiTagDto>("""{"id":8,"slug":"pvp","label":"PvP","published_count":11}""")
        assertEquals("PvP", tag.label)
        assertEquals(11, tag.publishedCount)
    }

    @Test fun supportPageDecodesSenderActor() {
        val dto = json.decodeFromString<SupportPageDto>(
            """{"pageId":"0x1A2B","type":"other","message":"stuck",
                "handled":false,"sender":{"name":"Gwen","account":"gwen01"}}""",
        )
        assertEquals("0x1A2B", dto.pageId)
        assertEquals("Gwen", dto.sender?.name)
        assertEquals("gwen01", dto.sender?.account)
        assertEquals(false, dto.handled)
    }

    @Test fun supportPageToleratesMissingSender() {
        val dto = json.decodeFromString<SupportPageDto>("""{"pageId":"0x01"}""")
        assertNull(dto.sender)
        assertNull(dto.type)
    }

    @Test fun siteModeStateDecodesAudit() {
        val dto = json.decodeFromString<SiteModeStateDto>(
            """{"site_mode":"maintenance","changed_at":"2026-07-22T08:00:00Z","changed_by":"admin"}""",
        )
        assertEquals("maintenance", dto.siteMode)
        assertEquals("admin", dto.changedBy)
    }

    @Test fun requestBodiesEncodeWithSnakeCaseKeys() {
        assertTrue(json.encodeToString(SiteModeRequest("maintenance")).contains("\"mode\":\"maintenance\""))
        assertTrue(json.encodeToString(PublishRequest(true)).contains("\"published\":true"))
        assertTrue(json.encodeToString(UnbanRequest("gwen01")).contains("\"account\":\"gwen01\""))
        assertTrue(json.encodeToString(BroadcastRequest("hello", hue = 33)).contains("\"hue\":33"))
        assertTrue(json.encodeToString(PageRespondRequest("done", close = true)).contains("\"close\":true"))
        assertTrue(json.encodeToString(WikiCategoryRequest(slug = "lore", title = "Lore", sortOrder = 1))
            .contains("\"sort_order\":1"))

        val post = json.encodeToString(PostCreateRequest(category = "news", title = "T", imageUrl = "/img.png"))
        assertTrue(post.contains("\"image_url\":\"/img.png\""))
        assertTrue(post.contains("\"category\":\"news\""))

        val ban = json.encodeToString(BanRequest(account = "x", durationSec = 3600, reason = "afk"))
        assertTrue(ban.contains("\"durationSec\":3600"))

        val kick = json.encodeToString(KickRequest(serial = "0xFF"))
        assertTrue(kick.contains("\"serial\":\"0xFF\""))
    }
}
