package wang.yaojia.webterm.api.routes

import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import wang.yaojia.webterm.testsupport.FakeHttpTransport
import wang.yaojia.webterm.wire.HostEndpoint
import wang.yaojia.webterm.wire.HttpMethod

/** The intentionally-LOOSE FCM token validator (plan §4.5) + register/unregister error mapping. */
class FcmTokenTest {
    private companion object {
        const val BASE = "http://h:3000"
        const val URL = "$BASE/push/fcm-token"
    }

    private val transport = FakeHttpTransport()
    private val client = ApiClient(HostEndpoint.fromBaseUrl(BASE)!!, transport)

    @Test
    fun acceptsBase64UrlPlusColonAndIsCaseSensitive() {
        // base64url chars + ':' , mixed case preserved (FCM tokens are case-sensitive → not lowered).
        val token = "cRZ7-x_Y9:APA91bH-Ab_CdEf"
        assertEquals(token, FcmTokenRule.normalize(token))
    }

    @Test
    fun rejectsEmptyOverlongAndOutOfCharsetTokens() {
        assertNull(FcmTokenRule.normalize(""))
        assertNull(FcmTokenRule.normalize("a".repeat(4097)))
        assertNull(FcmTokenRule.normalize("has space"))
        assertNull(FcmTokenRule.normalize("has/slash"))
        assertNull(FcmTokenRule.normalize("emoji😀"))
        assertNotNull(FcmTokenRule.normalize("a".repeat(4096))) // exactly at the bound is fine
    }

    @Test
    fun invalidTokenIsRejectedBeforeAnyNetworkIO() = runTest {
        val error = runCatching { client.registerFcmToken("bad token") }.exceptionOrNull()
        assertEquals(ApiClientError.InvalidFcmToken, error)
        assertTrue(transport.recordedRequests.isEmpty(), "must not hit the network for an invalid token")
    }

    @Test
    fun registerMaps400To403To429() = runTest {
        transport.queueSuccess(method = HttpMethod.POST, url = URL, status = 400)
        assertEquals(ApiClientError.InvalidFcmToken, runCatching { client.registerFcmToken("okTok") }.exceptionOrNull())

        transport.queueSuccess(method = HttpMethod.POST, url = URL, status = 403)
        assertEquals(ApiClientError.Forbidden, runCatching { client.registerFcmToken("okTok") }.exceptionOrNull())

        transport.queueSuccess(method = HttpMethod.POST, url = URL, status = 429)
        assertEquals(ApiClientError.RateLimited, runCatching { client.registerFcmToken("okTok") }.exceptionOrNull())
    }

    @Test
    fun registerAndUnregisterSucceedOn204() = runTest {
        transport.queueSuccess(method = HttpMethod.POST, url = URL, status = 204)
        transport.queueSuccess(method = HttpMethod.DELETE, url = URL, status = 204)
        // No exception = success.
        client.registerFcmToken("okTok")
        client.unregisterFcmToken("okTok")
        assertEquals(2, transport.recordedRequests.size)
    }
}
