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 org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue

class AVTransportClientTest {

    private lateinit var server: MockWebServer
    private lateinit var client: AVTransportClient

    @BeforeEach
    fun setUp() {
        server = MockWebServer()
        server.start()
        val controlUrl = server.url("/MediaRenderer/AVTransport/Control")
        client = AVTransportClient(SoapClient(OkHttpClient()), controlUrl)
    }

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

    @Test
    fun `seek sends Target in HH MM SS format`() = runTest {
        server.enqueue(emptyResponse("Seek"))
        client.seek(positionMs = 65_000L)
        val body = server.takeRequest().body.readUtf8()
        assertTrue(body.contains("<Target>0:01:05</Target>")) { "body was $body" }
    }

    @Test
    fun `getPositionInfo parses Track, RelTime and TrackDuration`() = runTest {
        server.enqueue(
            MockResponse().setBody(
                """<?xml version="1.0"?>
                   <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
                     <s:Body>
                       <u:GetPositionInfoResponse
                           xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                         <Track>3</Track>
                         <TrackDuration>0:03:30</TrackDuration>
                         <TrackURI>http://x/y.mp3</TrackURI>
                         <RelTime>0:01:05</RelTime>
                       </u:GetPositionInfoResponse>
                     </s:Body>
                   </s:Envelope>""".trimIndent(),
            ),
        )
        val info = client.getPositionInfo()
        assertEquals(3, info.track)
        assertEquals(65_000L, info.relTimeMs)
        assertEquals(210_000L, info.trackDurationMs)
        assertEquals("http://x/y.mp3", info.trackUri)
    }

    @Test
    fun `getTransportInfo maps PAUSED_PLAYBACK to PAUSED`() = runTest {
        server.enqueue(
            MockResponse().setBody(
                """<?xml version="1.0"?>
                   <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
                     <s:Body>
                       <u:GetTransportInfoResponse
                           xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                         <CurrentTransportState>PAUSED_PLAYBACK</CurrentTransportState>
                         <CurrentTransportStatus>OK</CurrentTransportStatus>
                         <CurrentSpeed>1</CurrentSpeed>
                       </u:GetTransportInfoResponse>
                     </s:Body>
                   </s:Envelope>""".trimIndent(),
            ),
        )
        val info = client.getTransportInfo()
        assertEquals(TransportState.PAUSED, info.state)
    }

    @Test
    fun `getTransportInfo maps unknown state to UNKNOWN`() = runTest {
        server.enqueue(
            MockResponse().setBody(
                """<?xml version="1.0"?>
                   <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
                     <s:Body>
                       <u:GetTransportInfoResponse
                           xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
                         <CurrentTransportState>BUFFERING_PLAYBACK</CurrentTransportState>
                         <CurrentTransportStatus>OK</CurrentTransportStatus>
                         <CurrentSpeed>1</CurrentSpeed>
                       </u:GetTransportInfoResponse>
                     </s:Body>
                   </s:Envelope>""".trimIndent(),
            ),
        )
        val info = client.getTransportInfo()
        assertEquals(TransportState.UNKNOWN, info.state)
    }

    @Test
    fun `removeAllTracksFromQueue sends correct SOAP action`() = runTest {
        server.enqueue(emptyResponse("RemoveAllTracksFromQueue"))
        client.removeAllTracksFromQueue()
        val request = server.takeRequest()
        assertTrue(request.getHeader("SOAPACTION").orEmpty().contains("RemoveAllTracksFromQueue")) {
            "SOAPACTION header missing action: ${request.getHeader("SOAPACTION")}"
        }
        val body = request.body.readUtf8()
        assertTrue(body.contains("RemoveAllTracksFromQueue")) { "body: $body" }
    }

    @Test
    fun `addURIToQueue sends EnqueuedURI and DIDL-Lite metadata`() = runTest {
        server.enqueue(emptyResponse("AddURIToQueue"))
        client.addURIToQueue(
            uri = "http://x/y.mp3",
            mime = "audio/mpeg",
            title = "Song",
            enqueuedURIPosition = 2,
        )
        val body = server.takeRequest().body.readUtf8()
        assertTrue(body.contains("<EnqueuedURI>http://x/y.mp3</EnqueuedURI>")) {
            "body missing EnqueuedURI: $body"
        }
        val positionTag = "<DesiredFirstTrackNumberEnqueued>2</DesiredFirstTrackNumberEnqueued>"
        assertTrue(body.contains(positionTag)) { "body missing position: $body" }
        assertTrue(body.contains("&lt;dc:title&gt;Song&lt;/dc:title&gt;")) {
            "title missing in DIDL: $body"
        }
        assertTrue(body.contains("audio/mpeg")) { "mime missing: $body" }
    }

    @Test
    fun `next sends Next SOAP action`() = runTest {
        server.enqueue(emptyResponse("Next"))
        client.next()
        val request = server.takeRequest()
        assertTrue(request.getHeader("SOAPACTION").orEmpty().endsWith("#Next\"")) {
            "SOAPACTION: ${request.getHeader("SOAPACTION")}"
        }
    }

    @Test
    fun `previous sends Previous SOAP action`() = runTest {
        server.enqueue(emptyResponse("Previous"))
        client.previous()
        val request = server.takeRequest()
        assertTrue(request.getHeader("SOAPACTION").orEmpty().endsWith("#Previous\"")) {
            "SOAPACTION: ${request.getHeader("SOAPACTION")}"
        }
    }

    @Test
    fun `seekToTrack sends TRACK_NR unit with 1-based target`() = runTest {
        server.enqueue(emptyResponse("Seek"))
        client.seekToTrack(trackNumber = 4)
        val body = server.takeRequest().body.readUtf8()
        assertTrue(body.contains("<Unit>TRACK_NR</Unit>")) { "body: $body" }
        assertTrue(body.contains("<Target>4</Target>")) { "body: $body" }
    }

    @Test
    fun `setAVTransportURIWithMetadata sends plain https URI for music track`() = runTest {
        server.enqueue(emptyResponse("SetAVTransportURI"))
        client.setAVTransportURIWithMetadata(
            uri = "https://example.com/track.mp3",
            mime = "audio/mpeg",
            title = "Song",
        )
        val body = server.takeRequest().body.readUtf8()
        assertTrue(body.contains("<CurrentURI>https://example.com/track.mp3</CurrentURI>")) {
            "CurrentURI should be sent as plain https: $body"
        }
        assertFalse(body.contains("x-rincon-mp3radio")) {
            "x-rincon-mp3radio scheme should not appear: $body"
        }
    }

    private fun emptyResponse(action: String): MockResponse = MockResponse().setBody(
        """<?xml version="1.0"?>
           <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
             <s:Body>
               <u:${action}Response xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"/>
             </s:Body>
           </s:Envelope>""".trimIndent(),
    )
}
