package zio.keeper.swim

import zio._
import zio.keeper.{ ByteCodec, KeeperSpec, NodeAddress }
import zio.keeper.swim.Messages.WithPiggyback
import zio.keeper.swim.PingPong.{ Ping, Pong }
import zio.logging.Logging
import zio.stream.ZStream
import zio.test.Assertion._
import zio.test._

object MessagesSpec extends KeeperSpec {

  val logger = Logging.console((_, line) => line)

  val messages = for {
    local     <- NodeAddress.local(1111).toManaged_
    transport <- TestTransport.make
    broadcast <- Broadcast.make(64000, 2).toManaged_
    messages  <- Messages.make(local, broadcast, transport)
  } yield (transport, messages)

  val spec = suite("messages")(
    testM("receiveMessage") {
      val testNodeAddress = NodeAddress(Array(1, 2, 3, 4), 1111)

      val protocol = Protocol[PingPong].make(
        {
          case Message.Direct(sender, _, Ping(i)) =>
            Message.direct(sender, Pong(i))
          case _ =>
            Message.noResponse
        },
        ZStream.empty
      )

      messages.use {
        case (testTransport, messages) =>
          for {
            dl    <- protocol
            _     <- messages.process(dl.binary)
            ping1 <- ByteCodec[PingPong].toChunk(PingPong.Ping(123))
            ping2 <- ByteCodec[PingPong].toChunk(PingPong.Ping(321))
            _     <- testTransport.incommingMessage(WithPiggyback(testNodeAddress, 1, ping1, List.empty))
            _     <- testTransport.incommingMessage(WithPiggyback(testNodeAddress, 2, ping2, List.empty))
            messages <- testTransport.outgoingMessages
                         .mapM {
                           case WithPiggyback(_, _, chunk, _) =>
                             ByteCodec.decode[PingPong](chunk)
                         }
                         .take(2)
                         .runCollect
          } yield assert(messages)(hasSameElements(List(PingPong.Pong(123), PingPong.Pong(321))))
      }
    },
    testM("should not exceed size of message") {
      val testNodeAddress = NodeAddress(Array(1, 2, 3, 4), 1111)

      val protocol = Protocol[PingPong].make(
        {
          case Message.Direct(sender, _, Ping(i)) =>
            ZIO.succeed(
              Message.Batch(
                Message.Direct(sender, 1, Pong(i)),
                Message.Broadcast(Pong(i)),
                List.fill(2000)(Message.Broadcast(Ping(1))): _*
              )
            )
          case _ =>
            Message.noResponse
        },
        ZStream.empty
      )

      messages.use {
        case (testTransport, messages) =>
          for {
            dl    <- protocol
            _     <- messages.process(dl.binary)
            ping1 <- ByteCodec[PingPong].toChunk(PingPong.Ping(123))
            _     <- testTransport.incommingMessage(WithPiggyback(testNodeAddress, 1, ping1, List.empty))
            m :: Nil <- testTransport.outgoingMessages
                         .take(1)
                         .runCollect
            bytes <- ByteCodec[WithPiggyback].toChunk(m)
          } yield assert(m.gossip.size)(equalTo(1453)) && assert(bytes.size)(equalTo(62580))
      }
    }
  ).provideCustomLayer(logger ++ ConversationId.live)
}