package com.bytelyst.platform

import kotlinx.coroutines.test.runTest
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class BLPlatformClientTest {

    private lateinit var server: MockWebServer
    private lateinit var config: BLPlatformConfig

    @BeforeEach
    fun setup() {
        server = MockWebServer()
        server.start()
        config = BLPlatformConfig(
            productId = "testapp",
            baseUrl = server.url("/api").toString().trimEnd('/'),
            applicationId = "com.test.app",
            timeoutMs = 5_000L,
        )
    }

    @AfterEach
    fun teardown() {
        server.shutdown()
    }

    @Test
    fun `GET request returns response body`() = runTest {
        server.enqueue(MockResponse().setBody("""{"ok":true}""").setResponseCode(200))

        val client = BLPlatformClient(config)
        val result = client.request("GET", "/health")

        assertEquals("""{"ok":true}""", result)
        val recorded = server.takeRequest()
        assertEquals("GET", recorded.method)
        assertTrue(recorded.path!!.endsWith("/api/health"))
    }

    @Test
    fun `POST request sends body`() = runTest {
        server.enqueue(MockResponse().setBody("""{"id":"1"}""").setResponseCode(201))

        val client = BLPlatformClient(config)
        val result = client.request("POST", "/items", body = """{"name":"test"}""")

        assertEquals("""{"id":"1"}""", result)
        val recorded = server.takeRequest()
        assertEquals("POST", recorded.method)
        assertEquals("""{"name":"test"}""", recorded.body.readUtf8())
    }

    @Test
    fun `injects X-Product-Id header`() = runTest {
        server.enqueue(MockResponse().setBody("{}").setResponseCode(200))

        val client = BLPlatformClient(config)
        client.request("GET", "/test")

        val recorded = server.takeRequest()
        assertEquals("testapp", recorded.getHeader("X-Product-Id"))
    }

    @Test
    fun `injects X-Request-Id header`() = runTest {
        server.enqueue(MockResponse().setBody("{}").setResponseCode(200))

        val client = BLPlatformClient(config)
        client.request("GET", "/test")

        val recorded = server.takeRequest()
        val requestId = recorded.getHeader("X-Request-Id")
        assertNotNull(requestId)
        assertTrue(requestId!!.isNotBlank())
    }

    @Test
    fun `injects Authorization header when token provided`() = runTest {
        server.enqueue(MockResponse().setBody("{}").setResponseCode(200))

        val client = BLPlatformClient(config) { "my-token-123" }
        client.request("GET", "/test")

        val recorded = server.takeRequest()
        assertEquals("Bearer my-token-123", recorded.getHeader("Authorization"))
    }

    @Test
    fun `omits Authorization header when no token`() = runTest {
        server.enqueue(MockResponse().setBody("{}").setResponseCode(200))

        val client = BLPlatformClient(config) { null }
        client.request("GET", "/test")

        val recorded = server.takeRequest()
        assertNull(recorded.getHeader("Authorization"))
    }

    @Test
    fun `skips auth when skipAuth is true`() = runTest {
        server.enqueue(MockResponse().setBody("{}").setResponseCode(200))

        val client = BLPlatformClient(config) { "should-not-appear" }
        client.request("GET", "/test", skipAuth = true)

        val recorded = server.takeRequest()
        assertNull(recorded.getHeader("Authorization"))
    }

    @Test
    fun `throws BLApiException on non-2xx`() = runTest {
        server.enqueue(MockResponse().setBody("""{"error":"not found"}""").setResponseCode(404))

        val client = BLPlatformClient(config)
        val ex = assertThrows(BLApiException::class.java) {
            kotlinx.coroutines.runBlocking {
                client.request("GET", "/missing")
            }
        }
        assertEquals(404, ex.statusCode)
        assertTrue(ex.responseBody.contains("not found"))
    }

    @Test
    fun `fireAndForget swallows errors`() = runTest {
        server.enqueue(MockResponse().setResponseCode(500))

        val client = BLPlatformClient(config)
        // Should not throw
        client.fireAndForget("POST", "/telemetry", body = """{"event":"test"}""")

        assertEquals(1, server.requestCount)
    }

    @Test
    fun `PUT request works`() = runTest {
        server.enqueue(MockResponse().setBody("""{"updated":true}""").setResponseCode(200))

        val client = BLPlatformClient(config)
        val result = client.request("PUT", "/items/1", body = """{"name":"updated"}""")

        assertEquals("""{"updated":true}""", result)
        val recorded = server.takeRequest()
        assertEquals("PUT", recorded.method)
    }

    @Test
    fun `DELETE request works`() = runTest {
        server.enqueue(MockResponse().setBody("").setResponseCode(204))

        val client = BLPlatformClient(config)
        val result = client.request("DELETE", "/items/1")

        assertEquals("", result)
        val recorded = server.takeRequest()
        assertEquals("DELETE", recorded.method)
    }

    @Test
    fun `extra headers are sent`() = runTest {
        server.enqueue(MockResponse().setBody("{}").setResponseCode(200))

        val client = BLPlatformClient(config)
        client.request("GET", "/test", extraHeaders = mapOf("X-Custom" to "value"))

        val recorded = server.takeRequest()
        assertEquals("value", recorded.getHeader("X-Custom"))
    }
}
