package com.tt.honeyDue.network

import androidx.test.core.app.ApplicationProvider
import coil3.Image
import coil3.PlatformContext
import coil3.decode.DataSource
import coil3.intercept.Interceptor
import coil3.network.HttpException
import coil3.network.NetworkHeaders
import coil3.network.NetworkResponse
import coil3.network.httpHeaders
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.ImageResult
import coil3.request.SuccessResult
import coil3.size.Size
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue

/**
 * Unit tests for [CoilAuthInterceptor].
 *
 * The interceptor is responsible for:
 *  1. Attaching `Authorization: <scheme> <token>` to image requests.
 *  2. On HTTP 401, calling the refresh callback once and retrying the
 *     request with the new token.
 *  3. Not looping: if the retry also returns 401, the error is returned.
 *  4. When no token is available, the request proceeds unauthenticated.
 *
 * Runs under Robolectric so Coil's Android `PlatformContext` (= `Context`)
 * is available for constructing `ImageRequest` values.
 */
@RunWith(RobolectricTestRunner::class)
class CoilAuthInterceptorTest {

    private val platformContext: PlatformContext
        get() = ApplicationProvider.getApplicationContext()

    private fun makeRequest(): ImageRequest =
        ImageRequest.Builder(platformContext)
            .data("https://example.com/media/1")
            .build()

    private fun makeSuccess(request: ImageRequest): SuccessResult =
        SuccessResult(
            image = FakeImage(),
            request = request,
            dataSource = DataSource.NETWORK
        )

    /** Minimal coil3.Image test-double — coil3 3.0.4 doesn't yet ship ColorImage. */
    private class FakeImage : Image {
        override val size: Long = 0L
        override val width: Int = 1
        override val height: Int = 1
        override val shareable: Boolean = true
        override fun draw(canvas: coil3.Canvas) {}
    }

    private fun make401Error(request: ImageRequest): ErrorResult {
        val response = NetworkResponse(code = 401, headers = NetworkHeaders.EMPTY)
        return ErrorResult(
            image = null,
            request = request,
            throwable = HttpException(response)
        )
    }

    private class FakeChain(
        initialRequest: ImageRequest,
        private val responses: MutableList<(ImageRequest) -> ImageResult>,
        val capturedRequests: MutableList<ImageRequest> = mutableListOf(),
    ) : Interceptor.Chain {
        private var currentRequest: ImageRequest = initialRequest

        override val request: ImageRequest get() = currentRequest
        override val size: Size = Size.ORIGINAL

        override fun withRequest(request: ImageRequest): Interceptor.Chain {
            currentRequest = request
            return this
        }

        override fun withSize(size: Size): Interceptor.Chain = this

        override suspend fun proceed(): ImageResult {
            capturedRequests += currentRequest
            val responder = responses.removeAt(0)
            return responder(currentRequest)
        }
    }

    @Test
    fun interceptor_attaches_authorization_header_when_token_present() = runTest {
        val request = makeRequest()
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf({ req -> makeSuccess(req) })
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { "abc123" },
            refreshToken = { null },
            authScheme = "Token",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is SuccessResult, "Expected success result")
        assertEquals(1, chain.capturedRequests.size)
        val sent = chain.capturedRequests.first()
        assertEquals("Token abc123", sent.httpHeaders["Authorization"])
    }

    @Test
    fun interceptor_skips_header_when_token_missing() = runTest {
        val request = makeRequest()
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf({ req -> makeSuccess(req) })
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { null },
            refreshToken = { null },
            authScheme = "Token",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is SuccessResult)
        assertEquals(1, chain.capturedRequests.size)
        val sent = chain.capturedRequests.first()
        // No Authorization header should have been added
        assertNull(sent.httpHeaders["Authorization"])
    }

    @Test
    fun interceptor_refreshes_and_retries_on_401() = runTest {
        val request = makeRequest()
        var refreshCallCount = 0
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf(
                { req -> make401Error(req) },       // first attempt -> 401
                { req -> makeSuccess(req) },        // retry -> 200
            )
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { "old-token" },
            refreshToken = {
                refreshCallCount++
                "new-token"
            },
            authScheme = "Token",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is SuccessResult, "Expected retry to succeed")
        assertEquals(1, refreshCallCount, "refreshToken should be invoked exactly once")
        assertEquals(2, chain.capturedRequests.size, "Expected original + 1 retry")
        assertEquals("Token old-token", chain.capturedRequests[0].httpHeaders["Authorization"])
        assertEquals("Token new-token", chain.capturedRequests[1].httpHeaders["Authorization"])
    }

    @Test
    fun interceptor_returns_error_when_refresh_returns_null() = runTest {
        val request = makeRequest()
        var refreshCallCount = 0
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf({ req -> make401Error(req) })
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { "old-token" },
            refreshToken = {
                refreshCallCount++
                null
            },
            authScheme = "Token",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is ErrorResult, "Expected error result when refresh fails")
        assertEquals(1, refreshCallCount, "refreshToken should be attempted once")
        // Only the first attempt should have gone through
        assertEquals(1, chain.capturedRequests.size)
    }

    @Test
    fun interceptor_does_not_loop_on_second_401() = runTest {
        val request = makeRequest()
        var refreshCallCount = 0
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf(
                { req -> make401Error(req) }, // first attempt -> 401
                { req -> make401Error(req) }, // retry also -> 401
            )
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { "old-token" },
            refreshToken = {
                refreshCallCount++
                "new-token"
            },
            authScheme = "Token",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is ErrorResult, "Second 401 should surface as ErrorResult")
        assertEquals(1, refreshCallCount, "refreshToken should be called exactly once — no infinite loop")
        assertEquals(2, chain.capturedRequests.size, "Expected original + exactly one retry")
    }

    @Test
    fun interceptor_passes_through_non_401_errors_without_refresh() = runTest {
        val request = makeRequest()
        var refreshCallCount = 0
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf({ req ->
                ErrorResult(
                    image = null,
                    request = req,
                    throwable = HttpException(
                        NetworkResponse(code = 500, headers = NetworkHeaders.EMPTY)
                    )
                )
            })
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { "tok" },
            refreshToken = {
                refreshCallCount++
                "should-not-be-called"
            },
            authScheme = "Token",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is ErrorResult)
        assertEquals(0, refreshCallCount, "refreshToken should not be invoked on non-401 errors")
        assertEquals(1, chain.capturedRequests.size)
    }

    @Test
    fun interceptor_supports_bearer_scheme() = runTest {
        val request = makeRequest()
        val chain = FakeChain(
            initialRequest = request,
            responses = mutableListOf({ req -> makeSuccess(req) })
        )
        val interceptor = CoilAuthInterceptor(
            tokenProvider = { "jwt.payload.sig" },
            refreshToken = { null },
            authScheme = "Bearer",
        )

        val result = interceptor.intercept(chain)

        assertTrue(result is SuccessResult)
        val sent = chain.capturedRequests.first()
        assertEquals("Bearer jwt.payload.sig", sent.httpHeaders["Authorization"])
    }
}
