package io.github.pauljamescleary.petstore
package infrastructure.endpoint

import domain.orders._
import infrastructure.repository.inmemory._
import cats.effect._
import io.circe._
import io.circe.generic.semiauto._
import org.http4s._
import org.http4s.implicits._
import org.http4s.dsl._
import org.http4s.circe._
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.server.Router
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import tsec.mac.jca.HMACSHA256
import org.scalatest.matchers.should.Matchers

class OrderEndpointsSpec
    extends AnyFunSuite
    with Matchers
    with ScalaCheckPropertyChecks
    with PetStoreArbitraries
    with Http4sDsl[IO]
    with Http4sClientDsl[IO] {
  implicit val statusDec: EntityDecoder[IO, OrderStatus] = jsonOf
  implicit val statusEnc: EntityEncoder[IO, OrderStatus] = jsonEncoderOf

  implicit val orderEncoder: Encoder[Order] = deriveEncoder
  implicit val orderEnc: EntityEncoder[IO, Order] = jsonEncoderOf
  implicit val orderDecoder: Decoder[Order] = deriveDecoder
  implicit val orderDec: EntityDecoder[IO, Order] = jsonOf

  def getTestResources(): (AuthTest[IO], HttpApp[IO]) = {
    val userRepo = UserRepositoryInMemoryInterpreter[IO]()
    val auth = new AuthTest[IO](userRepo)
    val orderService = OrderService(OrderRepositoryInMemoryInterpreter[IO]())
    val orderEndpoint =
      OrderEndpoints.endpoints[IO, HMACSHA256](orderService, auth.securedRqHandler)
    val orderRoutes = Router(("/orders", orderEndpoint)).orNotFound
    (auth, orderRoutes)
  }

  test("place and get order") {
    val (auth, orderRoutes) = getTestResources()

    forAll { (order: Order, user: AdminUser) =>
      (for {
        createRq <- POST(order, uri"/orders")
        createRqAuth <- auth.embedToken(user.value, createRq)
        createResp <- orderRoutes.run(createRqAuth)
        orderResp <- createResp.as[Order]
        getOrderRq <- GET(Uri.unsafeFromString(s"/orders/${orderResp.id.get}"))
        getOrderRqAuth <- auth.embedToken(user.value, getOrderRq)
        getOrderResp <- orderRoutes.run(getOrderRqAuth)
        orderResp2 <- getOrderResp.as[Order]
      } yield {
        createResp.status shouldEqual Ok
        orderResp.petId shouldBe order.petId
        getOrderResp.status shouldEqual Ok
        orderResp2.userId shouldBe defined
      }).unsafeRunSync
    }
  }

  test("user roles") {
    val (auth, orderRoutes) = getTestResources()

    forAll { user: CustomerUser =>
      (for {
        deleteRq <- DELETE(Uri.unsafeFromString(s"/orders/1"))
          .flatMap(auth.embedToken(user.value, _))
        deleteResp <- orderRoutes.run(deleteRq)
      } yield {
        deleteResp.status shouldEqual Unauthorized
      }).unsafeRunSync
    }

    forAll { user: AdminUser =>
      (for {
        deleteRq <- DELETE(Uri.unsafeFromString(s"/orders/1"))
          .flatMap(auth.embedToken(user.value, _))
        deleteResp <- orderRoutes.run(deleteRq)
      } yield {
        deleteResp.status shouldEqual Ok
      }).unsafeRunSync
    }
  }
}