package com.twitter.home_mixer.functional_component.feature_hydrator

import com.twitter.home_mixer.functional_component.feature_hydrator.RealGraphViewerAuthorFeatureHydrator.getCombinedRealGraphFeatures
import com.twitter.home_mixer.model.HomeFeatures.AuthorIdFeature
import com.twitter.home_mixer.model.HomeFeatures.InReplyToUserIdFeature
import com.twitter.home_mixer.util.MissingKeyException
import com.twitter.ml.api.DataRecord
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
import com.twitter.product_mixer.core.feature.Feature
import com.twitter.product_mixer.core.feature.FeatureWithDefaultOnFailure
import com.twitter.product_mixer.core.feature.datarecord.DataRecordInAFeature
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
import com.twitter.product_mixer.core.functional_component.feature_hydrator.CandidateFeatureHydrator
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
import com.twitter.product_mixer.core.pipeline.PipelineQuery
import com.twitter.stitch.Stitch
import com.twitter.timelines.prediction.adapters.real_graph.RealGraphEdgeFeaturesCombineAdapter
import com.twitter.timelines.prediction.adapters.real_graph.RealGraphFeaturesAdapter
import com.twitter.timelines.real_graph.v1.{thriftscala => v1}
import com.twitter.timelines.real_graph.{thriftscala => rg}
import com.twitter.util.Throw
import javax.inject.Inject
import javax.inject.Singleton
import scala.collection.JavaConverters._

object RealGraphViewerAuthorDataRecordFeature
    extends DataRecordInAFeature[TweetCandidate]
    with FeatureWithDefaultOnFailure[TweetCandidate, DataRecord] {
  override def defaultValue: DataRecord = new DataRecord()
}

object RealGraphViewerAuthorsDataRecordFeature
    extends DataRecordInAFeature[TweetCandidate]
    with FeatureWithDefaultOnFailure[TweetCandidate, DataRecord] {
  override def defaultValue: DataRecord = new DataRecord()
}

@Singleton
class RealGraphViewerAuthorFeatureHydrator @Inject() ()
    extends CandidateFeatureHydrator[PipelineQuery, TweetCandidate] {

  override val identifier: FeatureHydratorIdentifier =
    FeatureHydratorIdentifier("RealGraphViewerAuthor")

  override val features: Set[Feature[_, _]] =
    Set(RealGraphViewerAuthorDataRecordFeature, RealGraphViewerAuthorsDataRecordFeature)

  private val realGraphEdgeFeaturesAdapter = new RealGraphFeaturesAdapter
  private val realGraphEdgeFeaturesCombineAdapter =
    new RealGraphEdgeFeaturesCombineAdapter(prefix = "authors.realgraph")

  private val MissingKeyFeatureMap = FeatureMapBuilder()
    .add(RealGraphViewerAuthorDataRecordFeature, Throw(MissingKeyException))
    .add(RealGraphViewerAuthorsDataRecordFeature, Throw(MissingKeyException))
    .build()

  override def apply(
    query: PipelineQuery,
    candidate: TweetCandidate,
    existingFeatures: FeatureMap
  ): Stitch[FeatureMap] = {
    val viewerId = query.getRequiredUserId
    val realGraphFeatures = query.features
      .flatMap(_.getOrElse(RealGraphFeatures, None))
      .getOrElse(Map.empty[Long, v1.RealGraphEdgeFeatures])

    val result: FeatureMap = existingFeatures.getOrElse(AuthorIdFeature, None) match {
      case Some(authorId) =>
        val realGraphAuthorFeatures =
          getRealGraphViewerAuthorFeatures(viewerId, authorId, realGraphFeatures)
        val realGraphAuthorDataRecord = realGraphEdgeFeaturesAdapter
          .adaptToDataRecords(realGraphAuthorFeatures).asScala.headOption.getOrElse(new DataRecord)

        val combinedRealGraphFeaturesDataRecord = for {
          inReplyToAuthorId <- existingFeatures.getOrElse(InReplyToUserIdFeature, None)
        } yield {
          val combinedRealGraphFeatures =
            getCombinedRealGraphFeatures(Seq(authorId, inReplyToAuthorId), realGraphFeatures)
          realGraphEdgeFeaturesCombineAdapter
            .adaptToDataRecords(Some(combinedRealGraphFeatures)).asScala.headOption
            .getOrElse(new DataRecord)
        }

        FeatureMapBuilder()
          .add(RealGraphViewerAuthorDataRecordFeature, realGraphAuthorDataRecord)
          .add(
            RealGraphViewerAuthorsDataRecordFeature,
            combinedRealGraphFeaturesDataRecord.getOrElse(new DataRecord))
          .build()
      case _ => MissingKeyFeatureMap
    }
    Stitch(result)
  }

  private def getRealGraphViewerAuthorFeatures(
    viewerId: Long,
    authorId: Long,
    realGraphEdgeFeaturesMap: Map[Long, v1.RealGraphEdgeFeatures]
  ): rg.UserRealGraphFeatures = {
    realGraphEdgeFeaturesMap.get(authorId) match {
      case Some(realGraphEdgeFeatures) =>
        rg.UserRealGraphFeatures(
          srcId = viewerId,
          features = rg.RealGraphFeatures.V1(
            v1.RealGraphFeatures(edgeFeatures = Seq(realGraphEdgeFeatures))))
      case _ =>
        rg.UserRealGraphFeatures(
          srcId = viewerId,
          features = rg.RealGraphFeatures.V1(v1.RealGraphFeatures(edgeFeatures = Seq.empty)))
    }
  }
}

object RealGraphViewerAuthorFeatureHydrator {
  def getCombinedRealGraphFeatures(
    userIds: Seq[Long],
    realGraphEdgeFeaturesMap: Map[Long, v1.RealGraphEdgeFeatures]
  ): rg.RealGraphFeatures = {
    val edgeFeatures = userIds.flatMap(realGraphEdgeFeaturesMap.get)
    rg.RealGraphFeatures.V1(v1.RealGraphFeatures(edgeFeatures = edgeFeatures))
  }
}
