package com.tokilabs.proto_socket

import com.tokilabs.proto_socket.packets.HeartBeat
import com.tokilabs.proto_socket.packets.PacketBase
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.util.Collections
import kotlin.test.Test
import kotlin.test.assertTrue

private class HeartbeatTransport : Transport {
    val packets: MutableList<PacketBase> = Collections.synchronizedList(mutableListOf())

    override suspend fun writePacket(base: PacketBase) {
        packets.add(base)
    }

    override fun close() {
    }
}

private class HeartbeatClient(
    private val transport: HeartbeatTransport,
    intervalSec: Int,
    waitSec: Int,
) : BaseClient<HeartbeatClient>(
    intervalSec = intervalSec,
    waitSec = waitSec,
    doClose = { transport.close() },
) {
    override val self: HeartbeatClient
        get() = this

    override val communicator = Communicator(transport, testParserMap(), scope)

    init {
        communicator.addListener(typeNameOf<HeartBeat>()) { onHeartBeat() }
    }
}

class HeartbeatTest {
    @Test
    fun testHeartbeatSentAfterInactivity() = runBlocking {
        val transport = HeartbeatTransport()
        val client = HeartbeatClient(transport, 1, 1)

        try {
            client.sendHeartBeat()
            waitForCondition(message = "heartbeat was not sent") {
                transport.packets.any { it.typeName == typeNameOf<HeartBeat>() }
            }
        } finally {
            client.close()
        }
    }

    @Test
    fun testHeartbeatDisconnectOnNoResponse() = runBlocking {
        val transport = HeartbeatTransport()
        val client = HeartbeatClient(transport, 1, 1)
        var disconnected = false
        client.addDisconnectListener { disconnected = true }

        try {
            client.sendHeartBeat()
            delay(2_300)

            assertTrue(disconnected)
            assertTrue(!client.communicator.isAlive())
        } finally {
            client.close()
        }
    }

    @Test
    fun testHeartbeatTimerResetBySendHeartBeat() = runBlocking {
        val transport = HeartbeatTransport()
        val client = HeartbeatClient(transport, 1, 1)

        try {
            client.sendHeartBeat()
            delay(700)
            client.sendHeartBeat()
            delay(500)
            assertTrue(transport.packets.none { it.typeName == typeNameOf<HeartBeat>() })
            delay(700)
            assertTrue(transport.packets.any { it.typeName == typeNameOf<HeartBeat>() })
        } finally {
            client.close()
        }
    }

    @Test
    fun testHeartbeatTimerResetOnReceivedData() = runBlocking {
        val transport = HeartbeatTransport()
        val client = HeartbeatClient(transport, 1, 1)
        var received = false
        client.communicator.addListener(typeNameOf(testData())) {
            received = true
        }

        try {
            client.sendHeartBeat()
            delay(700)
            client.communicator.onReceivedData(
                typeNameOf(testData()),
                testData().toByteArray(),
                incomingNonce = 1,
            )
            // 비동기 inbound queue dispatch를 대기한다.
            var elapsed = 0
            while (!received && elapsed < 1000) {
                delay(10)
                elapsed += 10
            }
            assertTrue(received)
            client.sendHeartBeat()
            delay(500)
            assertTrue(transport.packets.none { it.typeName == typeNameOf<HeartBeat>() })
            delay(700)
            assertTrue(transport.packets.any { it.typeName == typeNameOf<HeartBeat>() })
        } finally {
            client.close()
        }
    }
}
