package xyz.cottongin.radio247.audio

import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
import java.io.ByteArrayInputStream
import java.net.ProtocolException

class StreamConnectionTest {

    private val server = MockWebServer()

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

    @Test
    fun sends_icy_metadata_header_and_reads_metaint_from_response() {
        server.enqueue(
            MockResponse()
                .setHeader("icy-metaint", "16000")
                .setHeader("Content-Type", "audio/mpeg")
                .setBody("fake audio data")
        )
        server.start()

        val conn = StreamConnection(server.url("/stream").toString())
        conn.open()

        val request = server.takeRequest()
        assertEquals("1", request.getHeader("Icy-MetaData"))
        assertEquals(16000, conn.metaint)
        assertNotNull(conn.inputStream)

        conn.close()
    }

    @Test
    fun metaint_is_null_when_server_does_not_provide_it() {
        server.enqueue(
            MockResponse()
                .setHeader("Content-Type", "audio/mpeg")
                .setBody("fake audio data")
        )
        server.start()

        val conn = StreamConnection(server.url("/stream").toString())
        conn.open()

        assertNull(conn.metaint)
        assertNotNull(conn.inputStream)

        conn.close()
    }

    @Test(expected = ConnectionFailed::class)
    fun throws_ConnectionFailed_on_HTTP_error() {
        server.enqueue(MockResponse().setResponseCode(404))
        server.start()

        val conn = StreamConnection(server.url("/stream").toString())
        conn.open()
    }

    @Test
    fun throws_ConnectionFailed_on_network_error() {
        server.start()
        val url = server.url("/stream").toString()
        server.shutdown()

        try {
            val conn = StreamConnection(url)
            conn.open()
            fail("Expected ConnectionFailed")
        } catch (e: ConnectionFailed) {
            assertTrue(e.message!!.contains("Network error"))
            assertNotNull(e.cause)
        }
    }

    @Test
    fun close_cleans_up_resources() {
        server.enqueue(
            MockResponse()
                .setHeader("icy-metaint", "16000")
                .setBody("fake audio data")
        )
        server.start()

        val conn = StreamConnection(server.url("/stream").toString())
        conn.open()
        assertNotNull(conn.inputStream)
        assertNotNull(conn.metaint)

        conn.close()

        assertNull(conn.inputStream)
        assertNull(conn.metaint)
    }

    @Test
    fun sends_user_agent_header() {
        server.enqueue(
            MockResponse()
                .setHeader("icy-metaint", "16000")
                .setBody("fake audio data")
        )
        server.start()

        val conn = StreamConnection(server.url("/stream").toString())
        conn.open()

        val request = server.takeRequest()
        assertEquals("Radio247/1.0", request.getHeader("User-Agent"))

        conn.close()
    }

    // --- ICY protocol header parsing (Shoutcast v1 support) ---

    @Test
    fun parseIcyHeaders_extracts_standard_shoutcast_headers() {
        val raw = "ICY 200 OK\r\n" +
            "icy-notice1:<BR>This stream requires Winamp<BR>\r\n" +
            "icy-name:My Station\r\n" +
            "icy-genre:Rock\r\n" +
            "content-type:audio/mpeg\r\n" +
            "icy-pub:1\r\n" +
            "icy-metaint:8192\r\n" +
            "icy-br:128\r\n" +
            "\r\n" +
            "audio bytes here"
        val input = ByteArrayInputStream(raw.toByteArray(Charsets.US_ASCII))

        val headers = StreamConnection.parseIcyHeaders(input)

        assertEquals("8192", headers["icy-metaint"])
        assertEquals("128", headers["icy-br"])
        assertEquals("audio/mpeg", headers["content-type"])
        assertEquals("My Station", headers["icy-name"])
        assertEquals("Rock", headers["icy-genre"])

        val remaining = input.readBytes().toString(Charsets.US_ASCII)
        assertEquals("audio bytes here", remaining)
    }

    @Test
    fun parseIcyHeaders_accepts_http_status_line() {
        val raw = "HTTP/1.0 200 OK\r\n" +
            "icy-metaint:16000\r\n" +
            "Content-Type:audio/mpeg\r\n" +
            "\r\n"
        val headers = StreamConnection.parseIcyHeaders(
            ByteArrayInputStream(raw.toByteArray(Charsets.US_ASCII))
        )
        assertEquals("16000", headers["icy-metaint"])
        assertEquals("audio/mpeg", headers["content-type"])
    }

    @Test(expected = ConnectionFailed::class)
    fun parseIcyHeaders_rejects_garbage_status_line() {
        val raw = "GARBAGE\r\n\r\n"
        StreamConnection.parseIcyHeaders(
            ByteArrayInputStream(raw.toByteArray(Charsets.US_ASCII))
        )
    }

    @Test
    fun parseIcyHeaders_handles_missing_metaint() {
        val raw = "ICY 200 OK\r\n" +
            "content-type:audio/mpeg\r\n" +
            "icy-br:64\r\n" +
            "\r\n"
        val headers = StreamConnection.parseIcyHeaders(
            ByteArrayInputStream(raw.toByteArray(Charsets.US_ASCII))
        )
        assertNull(headers["icy-metaint"])
        assertEquals("64", headers["icy-br"])
    }

    @Test
    fun parseIcyHeaders_handles_lf_only_line_endings() {
        val raw = "ICY 200 OK\n" +
            "icy-metaint:4096\n" +
            "content-type:audio/mpeg\n" +
            "\n"
        val headers = StreamConnection.parseIcyHeaders(
            ByteArrayInputStream(raw.toByteArray(Charsets.US_ASCII))
        )
        assertEquals("4096", headers["icy-metaint"])
    }

    @Test
    fun looksLikeIcyProtocolError_matches_protocol_exception() {
        assertTrue(
            StreamConnection.looksLikeIcyProtocolError(
                ProtocolException("unexpected status line: ICY 200 OK")
            )
        )
    }

    @Test
    fun looksLikeIcyProtocolError_matches_http09_message() {
        assertTrue(
            StreamConnection.looksLikeIcyProtocolError(
                java.io.IOException("Received HTTP/0.9 when not allowed")
            )
        )
    }

    @Test
    fun looksLikeIcyProtocolError_does_not_match_unrelated_io_error() {
        assertTrue(
            !StreamConnection.looksLikeIcyProtocolError(
                java.io.IOException("Connection reset by peer")
            )
        )
    }
}
