package wang.yaojia.webterm.api.models

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test

/**
 * PrStatus tolerant decode (plan Phase A.1): a full `availability:"ok"` body decodes every field; an
 * unknown/missing `availability` degrades to [PrAvailability.ERROR] (never throws); `PrCheckSummary`
 * counts round-trip; a non-object body degrades rather than crashing. Mirrors the FE never treating a
 * non-`ok` availability as an HTTP error.
 */
class PrStatusTest {

    private fun decode(json: String): PrStatus? =
        LossyDecode.objectOrNull(json.toByteArray(), PrStatus.serializer())

    @Test
    fun `decodes a full ok body with all fields and check counts`() {
        val json = """
          { "availability":"ok", "number":42, "title":"Add worktrees", "url":"https://x/pull/42",
            "state":"open", "isDraft":false, "mergeable":"mergeable",
            "headRefName":"feat/wt", "baseRefName":"main",
            "checks": { "total":5, "passing":3, "failing":1, "pending":1 } }
        """.trimIndent()

        val pr = decode(json)!!
        assertEquals(PrAvailability.OK, pr.availability)
        assertEquals(42, pr.number)
        assertEquals("Add worktrees", pr.title)
        assertEquals("https://x/pull/42", pr.url)
        assertEquals("open", pr.state)
        assertEquals(false, pr.isDraft)
        assertEquals("mergeable", pr.mergeable)
        assertEquals("feat/wt", pr.headRefName)
        assertEquals("main", pr.baseRefName)
        assertEquals(PrCheckSummary(total = 5, passing = 3, failing = 1, pending = 1), pr.checks)
    }

    @Test
    fun `an unknown availability degrades to ERROR, never throwing`() {
        val pr = decode("""{ "availability":"quantum-flux" }""")!!
        assertEquals(PrAvailability.ERROR, pr.availability)
    }

    @Test
    fun `a body missing availability defaults to ERROR and leaves optional fields null`() {
        val pr = decode("""{ "number":7 }""")!!
        assertEquals(PrAvailability.ERROR, pr.availability)
        assertEquals(7, pr.number)
        assertNull(pr.title)
        assertNull(pr.checks)
    }

    @Test
    fun `each known availability maps from its wire value`() {
        assertEquals(PrAvailability.NO_PR, PrAvailability.fromWire("no-pr"))
        assertEquals(PrAvailability.NOT_INSTALLED, PrAvailability.fromWire("not-installed"))
        assertEquals(PrAvailability.UNAUTHENTICATED, PrAvailability.fromWire("unauthenticated"))
        assertEquals(PrAvailability.DISABLED, PrAvailability.fromWire("disabled"))
        assertEquals(PrAvailability.ERROR, PrAvailability.fromWire("error"))
        assertEquals(PrAvailability.ERROR, PrAvailability.fromWire("")) // empty → ERROR
    }

    @Test
    fun `a non-object body degrades to null rather than throwing`() {
        assertNull(decode("[]"))
        assertNull(decode("not json"))
        assertNull(LossyDecode.objectOrNull(ByteArray(0), PrStatus.serializer()))
    }

    @Test
    fun `unknown top-level keys are ignored`() {
        val pr = decode("""{ "availability":"ok", "futureField":123, "nested":{"a":1} }""")!!
        assertEquals(PrAvailability.OK, pr.availability)
    }
}
