package com.yannick_cw.elastic_indexer4s.elasticsearch.index_ops

import cats.data.EitherT
import cats.implicits._
import com.yannick_cw.elastic_indexer4s.Index_results.{IndexError, StageSucceeded}

import scala.concurrent.{ExecutionContext, Future, blocking}
import scala.util.control.NonFatal

class AliasSwitching(esClient: EsOpsClientApi, waitForElastic: Long, minThreshold: Double, maxThreshold: Double)(
    implicit ec: ExecutionContext) {

  import esClient._

  def switchAlias(alias: String, newIndexName: String): Future[Either[IndexError, StageSucceeded]] =
    trySwitching(alias, newIndexName)
      .recover {
        case NonFatal(ex) =>
          Left(IndexError("Could not switch alias.", Some(ex)))
      }

  private def trySwitching(alias: String, newIndexName: String): Future[Either[IndexError, StageSucceeded]] =
    (for {
      _       <- EitherT.liftF[Future, IndexError, Unit](Future(blocking(Thread.sleep(waitForElastic))))
      oldSize <- latestIndexWithAliasSize(alias)
      newSize <- sizeFor(newIndexName)
      optSwitchRes <- oldSize
        .traverse(oldIndexSize => switchAliasBetweenIndices(oldIndexSize, newSize, alias, newIndexName))
      switchRes <- optSwitchRes match {
        case None =>
          addAliasToIndex(newIndexName, alias)
            .map(_ => NewAliasCreated(s"Added alias $alias to index $newIndexName"): StageSucceeded)
        case Some(x) => EitherT.pure[Future, IndexError](x)
      }
    } yield switchRes).value

  private def switchAliasBetweenIndices(oldSize: Long,
                                        newSize: Long,
                                        alias: String,
                                        newIndexName: String): OpsResult[StageSucceeded] = {
    val percentage = newSize / oldSize.toDouble
    if (checkThreshold(percentage))
      switchAliasToIndex(alias, newIndexName)
        .map(_ => AliasSwitched(s"Switched alias, new index size is ${(percentage * 100).toInt}% of old index"))
    else
      EitherT.leftT(
        IndexError(
          s"Switching failed, new index size is ${(percentage * 100).toInt}% of old index,\n" +
            s" $oldSize documents in old index with alias $alias, $newSize documents in new index $newIndexName.\n\n" +
            s"If you think the size of the new index is not correct, try to increase the `waitForElasticTimeout` property in the config." +
            s"This run spent ${waitForElastic / 1000} seconds waiting"))
  }

  private def checkThreshold(percentage: Double): Boolean = minThreshold < percentage && percentage <= maxThreshold
}

object AliasSwitching {
  def apply(esClient: EsOpsClientApi, minThreshold: Double, maxThreshold: Double, waitForElastic: Long)(
      implicit ec: ExecutionContext): AliasSwitching =
    new AliasSwitching(esClient, waitForElastic, minThreshold, maxThreshold)
}

case class AliasSwitched(override val msg: String)   extends StageSucceeded
case class NewAliasCreated(override val msg: String) extends StageSucceeded