package com.fabledsword.minstrel.player.output.upnp

import kotlinx.coroutines.test.runTest
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue

/**
 * Unit tests for SoapClient. Uses MockWebServer to capture the
 * outgoing SOAP envelope + verify the SOAPACTION header shape, and
 * to drive the `<s:Fault>` response path.
 *
 * kxml2 is on the test classpath (Task 4), so
 * `XmlPullParserFactory.newInstance()` resolves on the JVM.
 */
class SoapClientTest {
    private lateinit var server: MockWebServer
    private lateinit var client: SoapClient

    @BeforeEach
    fun setup() {
        server = MockWebServer().apply { start() }
        client = SoapClient(OkHttpClient())
    }

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

    @Test
    fun `SetAVTransportURI envelope sent with correct SOAPACTION header`() = runTest {
        server.enqueue(
            MockResponse()
                .setBody(setUriResponseBody())
                .setHeader("Content-Type", "text/xml"),
        )

        client.call(
            controlUrl = server.url("/avt/Control"),
            serviceType = "urn:schemas-upnp-org:service:AVTransport:1",
            action = "SetAVTransportURI",
            args = mapOf(
                "InstanceID" to "0",
                "CurrentURI" to "http://server/api/tracks/x/stream?token=t&exp=1",
                "CurrentURIMetaData" to "",
            ),
        )

        val recorded = server.takeRequest()
        assertEquals(
            "\"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\"",
            recorded.getHeader("SOAPACTION"),
        )
        val body = recorded.body.readUtf8()
        assertTrue(
            body.contains("<u:SetAVTransportURI"),
            "envelope missing <u:SetAVTransportURI: $body",
        )
        assertTrue(
            body.contains(
                "<CurrentURI>http://server/api/tracks/x/stream?token=t&amp;exp=1</CurrentURI>",
            ),
            "envelope missing escaped CurrentURI: $body",
        )
    }

    @Test
    fun `XML special chars in args are escaped`() = runTest {
        server.enqueue(
            MockResponse()
                .setBody(setUriResponseBody())
                .setHeader("Content-Type", "text/xml"),
        )

        client.call(
            controlUrl = server.url("/avt/Control"),
            serviceType = "urn:schemas-upnp-org:service:AVTransport:1",
            action = "SetAVTransportURI",
            args = mapOf("CurrentURI" to "a&b<c>\"d'e"),
        )

        val recorded = server.takeRequest()
        val body = recorded.body.readUtf8()
        assertTrue(
            body.contains("a&amp;b&lt;c&gt;&quot;d&apos;e"),
            "expected all five XML escapes in body, got: $body",
        )
    }

    @Test
    fun `SOAP fault becomes SoapFaultException`() = runTest {
        server.enqueue(
            MockResponse()
                .setResponseCode(500)
                .setBody(faultResponseBody())
                .setHeader("Content-Type", "text/xml"),
        )

        val ex = assertFailsWith<SoapFaultException> {
            client.call(
                controlUrl = server.url("/avt/Control"),
                serviceType = "urn:schemas-upnp-org:service:AVTransport:1",
                action = "Play",
                args = mapOf("InstanceID" to "0", "Speed" to "1"),
            )
        }
        assertEquals("402", ex.code)
    }

    private fun setUriResponseBody(): String = """
        <?xml version="1.0"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
                    s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
          <s:Body>
            <u:SetAVTransportURIResponse
                xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"/>
          </s:Body>
        </s:Envelope>
    """.trimIndent()

    private fun faultResponseBody(): String = """
        <?xml version="1.0"?>
        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
                    s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
          <s:Body>
            <s:Fault>
              <faultcode>s:Client</faultcode>
              <faultstring>UPnPError</faultstring>
              <detail>
                <UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
                  <errorCode>402</errorCode>
                  <errorDescription>Invalid Args</errorDescription>
                </UPnPError>
              </detail>
            </s:Fault>
          </s:Body>
        </s:Envelope>
    """.trimIndent()
}
