/*
 * Copyright 2018-2020 ProfunKtor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.profunktor.redis4cats

import cats.effect._
import cats.implicits._
import dev.profunktor.redis4cats.connection._
import dev.profunktor.redis4cats.data.RedisCodec
import dev.profunktor.redis4cats.effect.Log
import dev.profunktor.redis4cats.hlist._
import dev.profunktor.redis4cats.log4cats._
import dev.profunktor.redis4cats.transactions._
import java.util.concurrent.TimeoutException

object ConcurrentTransactionsDemo extends LoggerIOApp {

  import Demo._

  def program: IO[Unit] = {
    val key1 = "test1"
    val key2 = "test2"

    val showResult: String => Option[String] => IO[Unit] = key =>
      _.fold(Log[IO].info(s"Key not found: $key"))(s => Log[IO].info(s"$key: $s"))

    val mkClient: Resource[IO, RedisClient] =
      Resource.liftF(RedisURI.make[IO](redisURI)).flatMap(RedisClient[IO](_))

    val mkRedis: Resource[IO, RedisCommands[IO, String, String]] =
      mkClient.flatMap(cli => Redis[IO].fromClient(cli, RedisCodec.Utf8))

    def txProgram(v1: String, v2: String) =
      mkRedis
        .use { cmd =>
          val getters =
            cmd.get(key1).flatTap(showResult(key1)) *>
                cmd.get(key2).flatTap(showResult(key2))

          val operations =
            cmd.set(key1, "sad") :: cmd.set(key2, "windows") :: cmd.get(key1) ::
                cmd.set(key1, v1) :: cmd.set(key2, v2) :: cmd.get(key1) :: HNil

          val prog: IO[Unit] =
            RedisTransaction(cmd)
              .filterExec(operations)
              .flatMap {
                case res1 ~: res2 ~: HNil =>
                  Log[IO].info(s"res1: $res1, res2: $res2")
              }
              .onError {
                case TransactionAborted =>
                  Log[IO].error("[Error] - Transaction Aborted")
                case TransactionDiscarded =>
                  Log[IO].error("[Error] - Transaction Discarded")
                case _: TimeoutException =>
                  Log[IO].error("[Error] - Timeout")
              }

          val watching =
            cmd.watch(key1, key2)

          getters >> watching >> prog >> getters >> Log[IO].info("keep doing stuff...")
        }

    // Only the first transaction will be successful. The second one will be discarded.
    //IO.race(txProgram("nix", "linux"), txProgram("foo", "bar")).void

    def retriableTx: IO[Unit] =
      txProgram("foo", "bar").handleErrorWith {
        case TransactionDiscarded => retriableTx
      }.uncancelable

    // The first transaction will be successful but ultimately, the second transaction will retry and win
    IO.race(txProgram("nix", "linux"), retriableTx).void
  }

}