<commit_before>/*
 * Copyright 2017 HM Revenue & Customs
 *
 * 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 services.allocation

import connectors.{ AuthProviderClient, CSREmailClient, EmailClient, ExchangeObjects }
import model.Exceptions.OptimisticLockException
import model.{ Commands, exchange, persisted, command }
import model._
import model.command.{ AssessorAllocation, AssessorAllocations }
import model.persisted.eventschedules.Event
import repositories.application.GeneralApplicationRepository
import repositories.{ AssessorAllocationMongoRepository }
import services.allocation.AssessorAllocationService.CouldNotFindAssessorContactDetails
import services.events.EventsService
import services.stc.{ EventSink, StcEventService }
import uk.gov.hmrc.play.http.HeaderCarrier

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

object AssessorAllocationService extends AssessorAllocationService {

  val assessorAllocationRepo: AssessorAllocationMongoRepository = repositories.assessorAllocationRepository
  val applicationRepo: GeneralApplicationRepository = repositories.applicationRepository

  val eventsService = EventsService
  val eventService: StcEventService = StcEventService

  val authProviderClient = AuthProviderClient
  val emailClient = CSREmailClient

  case class CouldNotFindAssessorContactDetails(userId: String) extends Exception(userId)
}

trait AssessorAllocationService extends EventSink {

  def assessorAllocationRepo: AssessorAllocationMongoRepository
  def applicationRepo: GeneralApplicationRepository

  def eventsService: EventsService

  def emailClient: EmailClient
  def authProviderClient: AuthProviderClient

  def getAllocations(eventId: String): Future[exchange.AssessorAllocations] = {
    assessorAllocationRepo.allocationsForEvent(eventId).map { a => exchange.AssessorAllocations.apply(a) }
  }

  def getAllocation(eventId: String, userId: String): Future[Option[exchange.AssessorAllocation]] = {
    assessorAllocationRepo.find(userId, eventId).map { allocationOpt =>
      allocationOpt.map { allocation =>
        exchange.AssessorAllocation(allocation.id, allocation.status, model.exchange.AssessorSkill(allocation.allocatedAs, ""))
      }
    }
  }

  def allocate(newAllocations: command.AssessorAllocations)(implicit hc: HeaderCarrier): Future[Unit] = {
    assessorAllocationRepo.allocationsForEvent(newAllocations.eventId).flatMap {
      case Nil =>
        for {
          _ <- assessorAllocationRepo.save(persisted.AssessorAllocation.fromCommand(newAllocations))
          _ <- notifyNewlyAllocatedAssessors(newAllocations)
        } yield ()
      case existingAllocations => updateExistingAllocations(existingAllocations, newAllocations).map(_ => ())
    }
  }

  private def notifyNewlyAllocatedAssessors(allocations: command.AssessorAllocations)(implicit hc: HeaderCarrier): Future[Unit] = {
    getContactDetails(allocations).map {
      _.map { case (contactDetailsForUser, eventDetails, allocationForUser) =>
        emailClient.sendAssessorAllocatedToEvent(
          contactDetailsForUser.email,
          contactDetailsForUser.firstName + " " + contactDetailsForUser.lastName,
          eventDetails.date.toString("d MMMM YYYY"),
          allocationForUser.allocatedAs.displayText,
          eventDetails.eventType.toString,
          eventDetails.location.name,
          eventDetails.startTime.toString("ha")
        )
      }
    }.map(_ => ())
  }

  private def notifyAllocationChangedAssessors(allocations: command.AssessorAllocations)(implicit hc: HeaderCarrier): Future[Unit] = {
    getContactDetails(allocations).map { userInfo =>
      userInfo.map { case (contactDetailsForUser, eventDetails, allocationForUser) =>
        emailClient.sendAssessorEventAllocationChanged(
          contactDetailsForUser.email,
          contactDetailsForUser.firstName + " " + contactDetailsForUser.lastName,
          eventDetails.date.toString("d MMMM YYYY"),
          allocationForUser.allocatedAs.displayText,
          eventDetails.eventType.toString,
          eventDetails.location.name,
          eventDetails.startTime.toString("ha")
        )
      }
    }.map(_ => ())
  }

  private def notifyAllocationUnallocatedAssessors(
    allocations: command.AssessorAllocations
  )(implicit hc: HeaderCarrier): Future[Unit] = {
    val eligibleAllocations = allocations.copy(allocations = allocations.allocations.filterNot(_.status == AllocationStatuses.DECLINED))
    getContactDetails(eligibleAllocations).map { userInfo =>
      userInfo.map { case (contactDetailsForUser, eventDetails, _) =>
        emailClient.sendAssessorUnAllocatedFromEvent(
          contactDetailsForUser.email,
          contactDetailsForUser.firstName + " " + contactDetailsForUser.lastName,
          eventDetails.date.toString("d MMMM YYYY")
        )
      }
    }.map(_ => ())
  }

  private def getContactDetails(allocations: AssessorAllocations)(implicit hc: HeaderCarrier)
  : Future[Seq[(ExchangeObjects.Candidate, Event, AssessorAllocation)]] = {
    if (allocations.allocations.isEmpty) {
      Future.successful(Seq())
    } else {
      for {<commit_msg>Ensure email sent to assessors has the correct time format<commit_after>/*
 * Copyright 2017 HM Revenue & Customs
 *
 * 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 services.allocation

import connectors.{ AuthProviderClient, CSREmailClient, EmailClient, ExchangeObjects }
import model.Exceptions.OptimisticLockException
import model.{ Commands, exchange, persisted, command }
import model._
import model.command.{ AssessorAllocation, AssessorAllocations }
import model.persisted.eventschedules.Event
import repositories.application.GeneralApplicationRepository
import repositories.{ AssessorAllocationMongoRepository }
import services.allocation.AssessorAllocationService.CouldNotFindAssessorContactDetails
import services.events.EventsService
import services.stc.{ EventSink, StcEventService }
import uk.gov.hmrc.play.http.HeaderCarrier

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

object AssessorAllocationService extends AssessorAllocationService {

  val assessorAllocationRepo: AssessorAllocationMongoRepository = repositories.assessorAllocationRepository
  val applicationRepo: GeneralApplicationRepository = repositories.applicationRepository

  val eventsService = EventsService
  val eventService: StcEventService = StcEventService

  val authProviderClient = AuthProviderClient
  val emailClient = CSREmailClient

  case class CouldNotFindAssessorContactDetails(userId: String) extends Exception(userId)
}

trait AssessorAllocationService extends EventSink {

  def assessorAllocationRepo: AssessorAllocationMongoRepository
  def applicationRepo: GeneralApplicationRepository

  def eventsService: EventsService

  def emailClient: EmailClient
  def authProviderClient: AuthProviderClient

  def getAllocations(eventId: String): Future[exchange.AssessorAllocations] = {
    assessorAllocationRepo.allocationsForEvent(eventId).map { a => exchange.AssessorAllocations.apply(a) }
  }

  def getAllocation(eventId: String, userId: String): Future[Option[exchange.AssessorAllocation]] = {
    assessorAllocationRepo.find(userId, eventId).map { allocationOpt =>
      allocationOpt.map { allocation =>
        exchange.AssessorAllocation(allocation.id, allocation.status, model.exchange.AssessorSkill(allocation.allocatedAs, ""))
      }
    }
  }

  def allocate(newAllocations: command.AssessorAllocations)(implicit hc: HeaderCarrier): Future[Unit] = {
    assessorAllocationRepo.allocationsForEvent(newAllocations.eventId).flatMap {
      case Nil =>
        for {
          _ <- assessorAllocationRepo.save(persisted.AssessorAllocation.fromCommand(newAllocations))
          _ <- notifyNewlyAllocatedAssessors(newAllocations)
        } yield ()
      case existingAllocations => updateExistingAllocations(existingAllocations, newAllocations).map(_ => ())
    }
  }

  private def notifyNewlyAllocatedAssessors(allocations: command.AssessorAllocations)(implicit hc: HeaderCarrier): Future[Unit] = {
    getContactDetails(allocations).map {
      _.map { case (contactDetailsForUser, eventDetails, allocationForUser) =>
        emailClient.sendAssessorAllocatedToEvent(
          contactDetailsForUser.email,
          contactDetailsForUser.firstName + " " + contactDetailsForUser.lastName,
          eventDetails.date.toString("d MMMM YYYY"),
          allocationForUser.allocatedAs.displayText,
          eventDetails.eventType.toString,
          eventDetails.location.name,
          eventDetails.startTime.toString("h:ma")
        )
      }
    }.map(_ => ())
  }

  private def notifyAllocationChangedAssessors(allocations: command.AssessorAllocations)(implicit hc: HeaderCarrier): Future[Unit] = {
    getContactDetails(allocations).map { userInfo =>
      userInfo.map { case (contactDetailsForUser, eventDetails, allocationForUser) =>
        emailClient.sendAssessorEventAllocationChanged(
          contactDetailsForUser.email,
          contactDetailsForUser.firstName + " " + contactDetailsForUser.lastName,
          eventDetails.date.toString("d MMMM YYYY"),
          allocationForUser.allocatedAs.displayText,
          eventDetails.eventType.toString,
          eventDetails.location.name,
          eventDetails.startTime.toString("h:ma")
        )
      }
    }.map(_ => ())
  }

  private def notifyAllocationUnallocatedAssessors(
    allocations: command.AssessorAllocations
  )(implicit hc: HeaderCarrier): Future[Unit] = {
    val eligibleAllocations = allocations.copy(allocations = allocations.allocations.filterNot(_.status == AllocationStatuses.DECLINED))
    getContactDetails(eligibleAllocations).map { userInfo =>
      userInfo.map { case (contactDetailsForUser, eventDetails, _) =>
        emailClient.sendAssessorUnAllocatedFromEvent(
          contactDetailsForUser.email,
          contactDetailsForUser.firstName + " " + contactDetailsForUser.lastName,
          eventDetails.date.toString("d MMMM YYYY")
        )
      }
    }.map(_ => ())
  }

  private def getContactDetails(allocations: AssessorAllocations)(implicit hc: HeaderCarrier)
  : Future[Seq[(ExchangeObjects.Candidate, Event, AssessorAllocation)]] = {
    if (allocations.allocations.isEmpty) {
      Future.successful(Seq())
    } else {
      for {
{'fraction_non_alphanumeric': 0.08279975440750811, 'fraction_numerical': 0.0014033856679238662, 'mean_word_length': 4.4764649375600385, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2365096', 'n_tokens_mistral': 3124, 'n_tokens_neox': 3082, 'n_words': 625}