package controllers

import auth.{AuthAction, AuthenticatedRequest}
import de.knockoutwhist.cards.Hand
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
import exceptions.{CantPlayCardException, GameFullException, NotEnoughPlayersException, NotHostException, NotInThisGameException}
import logic.PodManager
import logic.game.PollingEvents.CardPlayed
import logic.game.PollingEvents.GameStarted
import logic.game.{GameLobby, PollingEvents}
import model.sessions.{PlayerSession, UserSession}
import model.users.User
import play.api.*
import play.api.libs.json.{JsArray, JsValue, Json}
import play.api.mvc.*
import util.WebUIUtils

import java.util.UUID
import javax.inject.*
import scala.concurrent.Future
import scala.util.Try
import scala.concurrent.ExecutionContext
@Singleton
class IngameController @Inject()(
                                  val controllerComponents: ControllerComponents,
                                  val authAction: AuthAction,
                                  val podManager: PodManager
                                ) extends BaseController {

  def game(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    game match {
      case Some(g) =>
        g.logic.getCurrentState match {
          case Lobby => Ok(views.html.lobby.lobby(Some(request.user), g))
          case InGame =>
            Ok(views.html.ingame.ingame(
              g.getPlayerByUser(request.user),
              g
            ))
          case SelectTrump =>
            Ok(views.html.ingame.selecttrump(
              g.getPlayerByUser(request.user),
              g.logic
            ))
          case TieBreak =>
            Ok(views.html.ingame.tie(
              g.getPlayerByUser(request.user),
              g.logic
            ))
          case _ =>
            InternalServerError(s"Invalid game state for in-game view. GameId: $gameId" + s" State: ${g.logic.getCurrentState}")
        }
      case None =>
        NotFound("Game not found")
    }
    //NotFound(s"Reached end of game method unexpectedly. GameId: $gameId")
  }
  def startGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    val result = Try {
      game match {
        case Some(g) =>
          g.startGame(request.user)
        case None =>
          NotFound("Game not found")
      }
    }
    if (result.isSuccess) {
      Redirect(routes.IngameController.game(gameId))
    } else {
      val throwable = result.failed.get
      throwable match {
        case _: NotInThisGameException =>
          BadRequest(throwable.getMessage)
        case _: NotHostException =>
          Forbidden(throwable.getMessage)
        case _: NotEnoughPlayersException =>
          BadRequest(throwable.getMessage)
        case _ =>
          InternalServerError(throwable.getMessage)
      }
    }
  }
  def kickPlayer(gameId: String, playerToKick: UUID): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    game.get.leaveGame(playerToKick)
    Redirect(routes.IngameController.game(gameId))
  }
  def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    game.get.leaveGame(request.user.id)
    Redirect(routes.MainMenuController.mainMenu())
  }
  def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    val result = Try {
      game match {
        case Some(g) =>
          g.addUser(request.user)
        case None =>
          NotFound("Game not found")
      }
    }
    if (result.isSuccess) {
      Redirect(routes.IngameController.game(gameId))
    } else {
      val throwable = result.failed.get
      throwable match {
        case _: GameFullException =>
          BadRequest(throwable.getMessage)
        case _: IllegalArgumentException =>
          BadRequest(throwable.getMessage)
        case _: IllegalStateException =>
          BadRequest(throwable.getMessage)
        case _ =>
          InternalServerError(throwable.getMessage)
      }
    }
  }
  def playCard(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => {
    val game = podManager.getGame(gameId)
    game match {
      case Some(g) =>
        val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption))
        cardIdOpt match {
          case Some(cardId) =>
            var optSession: Option[UserSession] = None
            val result = Try {
              val session = g.getUserSession(request.user.id)
              optSession = Some(session)
              session.lock.lock()
              g.playCard(session, cardId.toInt)
            }
            optSession.foreach(_.lock.unlock())
            if (result.isSuccess) {
              NoContent
            } else {
              val throwable = result.failed.get
              throwable match {
                case _: CantPlayCardException =>
                  BadRequest(throwable.getMessage)
                case _: NotInThisGameException =>
                  BadRequest(throwable.getMessage)
                case _: IllegalArgumentException =>
                  BadRequest(throwable.getMessage)
                case _: IllegalStateException =>
                  BadRequest(throwable.getMessage)
                case _ =>
                  InternalServerError(throwable.getMessage)
              }
            }
          case None =>
            BadRequest("cardId parameter is missing")
        }
      case None =>
        NotFound("Game not found")
    }
  }
  }
  def playDogCard(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => {
    val game = podManager.getGame(gameId)
    game match {
      case Some(g) => {
        val cardIdOpt = request.body.asFormUrlEncoded.flatMap(_.get("cardId").flatMap(_.headOption))
        var optSession: Option[UserSession] = None
        val result = Try {
          cardIdOpt match {
            case Some(cardId) if cardId == "skip" =>
              val session = g.getUserSession(request.user.id)
              optSession = Some(session)
              session.lock.lock()
              g.playDogCard(session, -1)
            case Some(cardId) =>
              val session = g.getUserSession(request.user.id)
              optSession = Some(session)
              session.lock.lock()
              g.playDogCard(session, cardId.toInt)
            case None =>
              throw new IllegalArgumentException("cardId parameter is missing")
          }
        }
        optSession.foreach(_.lock.unlock())
        if (result.isSuccess) {
          NoContent
        } else {
          val throwable = result.failed.get
          throwable match {
            case _: CantPlayCardException =>
              BadRequest(throwable.getMessage)
            case _: NotInThisGameException =>
              BadRequest(throwable.getMessage)
            case _: IllegalArgumentException =>
              BadRequest(throwable.getMessage)
            case _: IllegalStateException =>
              BadRequest(throwable.getMessage)
            case _ =>
              InternalServerError(throwable.getMessage)
          }
        }
      }
      case None =>
        NotFound("Game not found")
    }
  }
  }
  def playTrump(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    game match {
      case Some(g) =>
        val trumpOpt = request.body.asFormUrlEncoded.flatMap(_.get("trump").flatMap(_.headOption))
        trumpOpt match {
          case Some(trump) =>
            var optSession: Option[UserSession] = None
            val result = Try {
              val session = g.getUserSession(request.user.id)
              optSession = Some(session)
              session.lock.lock()
              g.selectTrump(session, trump.toInt)
            }
            optSession.foreach(_.lock.unlock())
            if (result.isSuccess) {
              NoContent
            } else {
              val throwable = result.failed.get
              throwable match {
                case _: IllegalArgumentException =>
                  BadRequest(throwable.getMessage)
                case _: NotInThisGameException =>
                  BadRequest(throwable.getMessage)
                case _: IllegalStateException =>
                  BadRequest(throwable.getMessage)
                case _ =>
                  InternalServerError(throwable.getMessage)
              }
            }
          case None =>
            BadRequest("trump parameter is missing")
        }
      case None =>
        NotFound("Game not found")
    }
  }
  def playTie(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
    val game = podManager.getGame(gameId)
    game match {
      case Some(g) =>
        val tieOpt = request.body.asFormUrlEncoded.flatMap(_.get("tie").flatMap(_.headOption))
        tieOpt match {
          case Some(tie) =>
            var optSession: Option[UserSession] = None
            val result = Try {
              val session = g.getUserSession(request.user.id)
              optSession = Some(session)
              session.lock.lock()
              g.selectTie(g.getUserSession(request.user.id), tie.toInt - 1)
            }
            optSession.foreach(_.lock.unlock())
            if (result.isSuccess) {
              NoContent
            } else {
              val throwable = result.failed.get
              throwable match {
                case _: IllegalArgumentException =>
                  BadRequest(throwable.getMessage)
                case _: NotInThisGameException =>
                  BadRequest(throwable.getMessage)
                case _: IllegalStateException =>
                  BadRequest(throwable.getMessage)
                case _ =>
                  InternalServerError(throwable.getMessage)
              }
            }
          case None =>
            BadRequest("tie parameter is missing")
        }
      case None =>
        NotFound("Game not found")
    }
  }

}