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

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.test.runTest
import okhttp3.ResponseBody.Companion.toResponseBody
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException

/** Unit tests for the typed-result plumbing (PLAN.md §7). */
class ApiResultTest {

    @Test fun okWrapsValue() = runTest {
        val result = safeApiCall { 42 }
        assertEquals(ApiResult.Ok(42), result)
    }

    @Test fun httpExceptionBecomesHttpError() = runTest {
        val result = safeApiCall {
            throw HttpException(Response.error<Any>(404, "nope".toResponseBody(null)))
        }
        assertTrue(result is ApiResult.HttpError)
        assertEquals(404, (result as ApiResult.HttpError).status)
    }

    @Test fun ioExceptionBecomesNetworkError() = runTest {
        val result = safeApiCall { throw IOException("offline") }
        assertTrue(result is ApiResult.NetworkError)
    }

    /**
     * A body the app can't decode (a field whose type doesn't match its DTO) throws a
     * [SerializationException] out of the Retrofit converter. It must degrade to a
     * server-side error the UI renders, not escape and crash the app — the guild-board
     * crash this fixes. `502` folds to [ui.ErrorKind.SERVER] via `toUiState`.
     */
    @Test fun serializationExceptionBecomesServerError() = runTest {
        val result = safeApiCall {
            throw kotlinx.serialization.SerializationException("Unexpected symbol 'm' at path: \$[0].members")
        }
        assertTrue(result is ApiResult.HttpError)
        assertEquals(502, (result as ApiResult.HttpError).status)
    }

    @Test fun cancellationIsRethrown() = runTest {
        assertThrows(CancellationException::class.java) {
            kotlinx.coroutines.runBlocking {
                safeApiCall { throw CancellationException("cancelled") }
            }
        }
    }

    @Test fun mapTransformsOnlyOk() {
        assertEquals(ApiResult.Ok(4), ApiResult.Ok(2).map { it * 2 })
        val err: ApiResult<Int> = ApiResult.HttpError(500)
        assertEquals(err, err.map { it * 2 })
    }
}
