package ca.ksamad.encore.mpd

import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * Drives [MpdConnection] against in-memory streams, so the whole text protocol (greeting, key/value
 * parsing, ACK handling, binary payloads) is exercised with no socket.
 */
class MpdConnectionTest {
    private fun conn(
        serverBytes: ByteArray,
        out: ByteArrayOutputStream = ByteArrayOutputStream(),
    ) = MpdConnection(ByteArrayInputStream(serverBytes), out)

    private fun conn(
        serverText: String,
        out: ByteArrayOutputStream = ByteArrayOutputStream(),
    ) = conn(serverText.toByteArray(Charsets.UTF_8), out)

    @Test
    fun handshake_parsesVersion() {
        val c = conn("OK MPD 0.23.5\n")
        assertEquals("0.23.5", c.handshake())
        assertEquals("0.23.5", c.protocolVersion)
    }

    @Test
    fun handshake_rejectsBadGreeting() {
        val c = conn("HELLO THERE\n")
        assertThrows(MpdConnectionException::class.java) { c.handshake() }
    }

    @Test
    fun execute_writesCommandWithNewline() {
        val out = ByteArrayOutputStream()
        val c = conn("OK MPD 0.23.5\nvolume: 50\nOK\n", out)
        c.handshake()
        c.execute("status")
        assertEquals("status\n", out.toString("UTF-8"))
    }

    @Test
    fun execute_parsesKeyValueResponse() {
        val c = conn("OK MPD 0.23.5\nvolume: 50\nstate: play\nOK\n")
        c.handshake()
        val resp = c.execute("status")
        assertEquals("50", resp["volume"])
        assertEquals("play", resp["state"])
        assertNull(resp["nope"])
    }

    @Test
    fun execute_ackBecomesTypedException() {
        val c = conn("OK MPD 0.23.5\nACK [2@0] {play} Bad song index\n")
        c.handshake()
        val ex = assertThrows(MpdAckException::class.java) { c.execute("play 99") }
        assertEquals(MpdAckException.Code.ARG, ex.code)
        assertEquals("play", ex.command)
        assertEquals("Bad song index", ex.serverMessage)
    }

    @Test
    fun execute_readsBinaryPayloadExactly() {
        // albumart-style response: metadata, a binary block, then OK.
        val payload = byteArrayOf(0x00, 0x10, 0x7F, 0xFF.toByte(), '\n'.code.toByte())
        val header = "OK MPD 0.23.5\nsize: 5\ntype: image/png\nbinary: 5\n"
        val trailer = "\nOK\n"
        val bytes = header.toByteArray() + payload + trailer.toByteArray()

        val c = conn(bytes)
        c.handshake()
        val resp = c.execute("albumart foo 0")

        assertEquals("image/png", resp["type"])
        assertArrayEquals(payload, resp.binary)
    }

    @Test
    fun executeList_splitsResponsesOnListOk() {
        // command_list_ok_begin: each sub-response ends with list_OK, then final OK.
        val server =
            "OK MPD 0.23.5\n" +
                "volume: 50\nstate: play\nlist_OK\n" +
                "file: a.mp3\nTitle: A\nlist_OK\n" +
                "OK\n"
        val out = ByteArrayOutputStream()
        val c = conn(server, out)
        c.handshake()

        val responses = c.executeList("status", "currentsong")
        assertEquals(2, responses.size)
        assertEquals("50", responses[0]["volume"])
        assertEquals("play", responses[0]["state"])
        assertEquals("a.mp3", responses[1]["file"])
        assertEquals("A", responses[1]["Title"])

        assertTrue(
            out.toString("UTF-8")
                .startsWith("command_list_ok_begin\nstatus\ncurrentsong\ncommand_list_end\n")
        )
    }

    @Test
    fun execute_throwsOnEofMidResponse() {
        val c = conn("OK MPD 0.23.5\nvolume: 50\n") // no OK terminator
        c.handshake()
        val ex = assertThrows(MpdConnectionException::class.java) { c.execute("status") }
        assertTrue(ex.message!!.contains("mid-response"))
    }
}
