package lol.http


import cats.implicits._
import cats.effect.{ContextShift, IO, Timer}
import fs2.concurrent.SignallingRef
import fs2.{Chunk, Stream}
import lol.http.ServerSentEvents._

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class ServerSentEventsTests extends Tests {

  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
  implicit val timer: Timer[IO] = IO.timer(ec)
  implicit val cs: ContextShift[IO] = IO.contextShift(ec)

  val App: Service = {
    case url"/" =>
      Ok("Hello")
    case url"/stream" =>
      Ok(Stream(Event("Hello"), Event("World")).covaryAll[IO, Event[String]])
    case url"/fakeStream" =>
      Ok("Hello").addHeaders(h"Content-Type" -> h"text/event-stream")
  }

  test("Valid string events stream") {
    withServer(Server.listen()(App)) { server =>
      await() {
        Client("localhost", server.port).runAndStop { client =>
          client.run(Get("/stream")) { response =>
            response.readAs[Stream[IO,Event[String]]].flatMap { eventStream =>
              eventStream.compile.toVector.map(_.toList)
            }
          }
        }
      } should be (List(Event("Hello"), Event("World")))
    }
  }

  test("Events stream should be stopped by server when client closes the connection") {
    val isRunning = SignallingRef[IO, Boolean](true).unsafeRunSync()

    val App: Service = {
      case url"/infiniteStream" =>
        val infiniteStream =
          Stream.sleep[IO](100.milliseconds).flatMap(_ => Stream.chunk(Chunk.bytes("LOL\n".getBytes("utf-8")))).repeat
        Ok(Content(infiniteStream.onFinalize(isRunning.set(false))))
    }

    withServer(Server.listen()(App)) { server =>
      await() {
        val client = Client("localhost", server.port)
        (IO.sleep(1.second) >> IO(client.stopSync())).unsafeRunAsync(_ => ())
        client.run(Get("/infiniteStream")) { response =>
          response.readAs[String]
        }
      }

      eventually({
        val t = isRunning.get.unsafeRunSync()
        t shouldBe false
      })
    }
  }

  test("Not an events stream") {
    withServer(Server.listen()(App)) { server =>
      the [Error] thrownBy await() {
        Client("localhost", server.port).runAndStop { client =>
          client.run(Get("/")) { response =>
            response.readAs[Stream[IO,Event[String]]].flatMap { eventStream =>
              eventStream.compile.toVector.map(_.toList)
            }
          }
        }
      } should be (Error.UnexpectedContentType())
    }
  }

  test("Invalid events stream ") {
    withServer(Server.listen()(App)) { server =>
      await() {
        Client("localhost", server.port).runAndStop { client =>
          client.run(Get("/fakeStream")) { response =>
            response.readAs[Stream[IO,Event[String]]].flatMap { eventStream =>
              eventStream.compile.toVector.map(_.toList)
            }
          }
        }
      } should be (Nil)
    }
  }

}