package controllers

import com.mohiva.play.silhouette.api.exceptions.ProviderException
import com.mohiva.play.silhouette.api.{ LoginEvent, LogoutEvent }
import com.mohiva.play.silhouette.impl.exceptions.IdentityNotFoundException
import com.mohiva.play.silhouette.impl.providers.{ CommonSocialProfile, CommonSocialProfileBuilder, SocialProvider }
import models.user.{ User, UserForms }
import play.api.i18n.MessagesApi
import services.user.AuthenticationEnvironment
import play.api.libs.concurrent.Execution.Implicits.defaultContext

import scala.concurrent.Future

@javax.inject.Singleton
class AuthenticationController @javax.inject.Inject() (
    override val messagesApi: MessagesApi,
    override val env: AuthenticationEnvironment
) extends BaseController {
  def signInForm = withSession { implicit request =>
    Future.successful(Ok(views.html.signin(request.identity, UserForms.signInForm)))
  }

  def authenticateCredentials = withSession { implicit request =>
    UserForms.signInForm.bindFromRequest.fold(
      form => Future.successful(BadRequest(views.html.signin(request.identity, form))),
      credentials => env.credentials.authenticate(credentials).flatMap { loginInfo =>
        val result = Redirect(controllers.routes.HomeController.index())
        env.identityService.retrieve(loginInfo).flatMap {
          case Some(user) => env.authenticatorService.create(loginInfo).flatMap { authenticator =>
            env.eventBus.publish(LoginEvent(user, request, request2Messages))
            env.authenticatorService.init(authenticator).flatMap(v => env.authenticatorService.embed(v, result))
          }
          case None => Future.failed(new IdentityNotFoundException("Couldn't find user."))
        }
      }.recover {
        case e: ProviderException =>
          Redirect(controllers.routes.AuthenticationController.signInForm()).flashing(("error", "Invalid credentials."))
      }
    )
  }

  def authenticateSocial(provider: String) = withSession { implicit request =>
    (env.providersMap.get(provider) match {
      case Some(p: SocialProvider with CommonSocialProfileBuilder) =>
        p.authenticate().flatMap {
          case Left(result) => Future.successful(result)
          case Right(authInfo) => for {
            profile <- p.retrieveProfile(authInfo)
            user <- env.userService.create(mergeUser(request.identity, profile), profile)
            authInfo <- env.authInfoService.save(profile.loginInfo, authInfo)
            authenticator <- env.authenticatorService.create(profile.loginInfo)
            value <- env.authenticatorService.init(authenticator)
            result <- env.authenticatorService.embed(value, Redirect(controllers.routes.HomeController.index()))
          } yield {
            env.eventBus.publish(LoginEvent(user, request, request2Messages))
            result
          }
        }
      case _ => Future.failed(new ProviderException("Invalid provider [" + provider + "]."))
    }).recover {
      case e: ProviderException =>
        logger.error("Unexpected provider error", e)
        Redirect(routes.AuthenticationController.signInForm()).flashing(("error", "Service error with provider [" + provider + "]."))
    }
  }

  def signOut = SecuredAction.async { implicit request =>
    val result = Redirect(controllers.routes.HomeController.index())
    env.eventBus.publish(LogoutEvent(request.identity, request, request2Messages))
    env.authenticatorService.discard(request.authenticator, result).map(x => result)
  }

  private[this] def mergeUser(user: User, profile: CommonSocialProfile) = {
    user.copy(
      username = if (profile.firstName.isDefined && user.username.isEmpty) { profile.firstName } else { user.username }
    )
  }
}