package dev.ulfrx.recipe.shared.dto

import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals

/**
 * Wire-format contract for [MeResponse] (D-27).
 *
 * Phase 2 pins the DTO shape used by both the Ktor server's `/api/v1/me` route and the
 * KMP client. The fields, JSON keys, and forward-compatibility behavior asserted here are
 * the load-bearing contract — downstream Phase 2 plans implement against it.
 */
class MeResponseSerializationTest {
    private val strictJson = Json { encodeDefaults = true }
    private val lenientJson =
        Json {
            ignoreUnknownKeys = true
            encodeDefaults = true
        }

    @Test
    fun `round trips id sub email and displayName via camelCase wire keys`() {
        val original =
            MeResponse(
                id = "11111111-2222-3333-4444-555555555555",
                sub = "authentik|abc",
                email = "anna@example.test",
                displayName = "Anna",
            )

        val encoded = strictJson.encodeToString(MeResponse.serializer(), original)

        // Wire keys must match the server contract exactly. Authentik's `sub` is opaque,
        // so we never rename it on the wire.
        assertEquals(
            "{\"id\":\"11111111-2222-3333-4444-555555555555\"," +
                "\"sub\":\"authentik|abc\"," +
                "\"email\":\"anna@example.test\"," +
                "\"displayName\":\"Anna\"}",
            encoded,
        )

        val decoded = strictJson.decodeFromString(MeResponse.serializer(), encoded)
        assertEquals(original, decoded)
    }

    @Test
    fun `decoder ignores future fields like householdId without failing`() {
        // Phase 3 will extend the response with `householdId` (D-28). The Phase 2 client
        // must keep parsing the response after that change, so the decoder must skip
        // unknown keys.
        val withFutureField =
            "{\"id\":\"11111111-2222-3333-4444-555555555555\"," +
                "\"sub\":\"authentik|abc\"," +
                "\"email\":\"anna@example.test\"," +
                "\"displayName\":\"Anna\"," +
                "\"householdId\":\"99999999-0000-0000-0000-000000000000\"}"

        val decoded = lenientJson.decodeFromString(MeResponse.serializer(), withFutureField)

        assertEquals("11111111-2222-3333-4444-555555555555", decoded.id)
        assertEquals("authentik|abc", decoded.sub)
        assertEquals("anna@example.test", decoded.email)
        assertEquals("Anna", decoded.displayName)
    }

    @Test
    fun `toUser maps every field one-to-one without dropping data`() {
        val response =
            MeResponse(
                id = "11111111-2222-3333-4444-555555555555",
                sub = "authentik|abc",
                email = "anna@example.test",
                displayName = "Anna",
            )

        val user = response.toUser()

        assertEquals(
            User(
                id = response.id,
                sub = response.sub,
                email = response.email,
                displayName = response.displayName,
            ),
            user,
        )
    }
}
