package de.nowchess.backcore.game

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import de.nowchess.api.board.Color
import de.nowchess.api.game.{DrawReason, GameResult as ApiGameResult}
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
import de.nowchess.backcore.dto.*
import de.nowchess.io.fen.FenExporter
import de.nowchess.io.pgn.PgnExporter
import de.nowchess.rules.sets.DefaultRules

object GameMapper:
  private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)

  def toGameFullJson(snapshot: GameSnapshot): String =
    mapper.writeValueAsString(toGameFull(snapshot))

  def toGameFull(snapshot: GameSnapshot): GameFullResponse =
    GameFullResponse(
      gameId = snapshot.gameId,
      white = toPlayerInfo(snapshot.white),
      black = toPlayerInfo(snapshot.black),
      state = toGameState(snapshot),
    )

  def toGameState(snapshot: GameSnapshot): GameStateResponse =
    val (status, winner) = computeStatus(snapshot)
    GameStateResponse(
      fen = FenExporter.exportGameContext(snapshot.context),
      pgn = buildPgn(snapshot.context.moves),
      turn = if snapshot.context.turn == Color.White then "white" else "black",
      status = status,
      winner = winner,
      moves = snapshot.context.moves.map(moveToUci),
      undoAvailable = snapshot.canUndo,
      redoAvailable = snapshot.canRedo,
    )

  private def toPlayerInfo(p: de.nowchess.api.player.PlayerInfo): PlayerInfoDto =
    PlayerInfoDto(id = p.id.value, displayName = p.displayName)

  private def colorStr(c: Color): String = if c == Color.White then "white" else "black"

  private def computeStatus(snapshot: GameSnapshot): (String, Option[String]) =
    snapshot.externalResult match
      case Some(GameResult.Resign(winner)) =>
        ("resign", Some(colorStr(winner)))
      case Some(GameResult.AgreedDraw) | Some(GameResult.FiftyMoveDraw) =>
        ("draw", None)
      case _ =>
        snapshot.context.result match
          case Some(ApiGameResult.Win(winner))                           => ("checkmate", Some(colorStr(winner)))
          case Some(ApiGameResult.Draw(DrawReason.Stalemate))            => ("stalemate", None)
          case Some(ApiGameResult.Draw(DrawReason.InsufficientMaterial)) => ("insufficientMaterial", None)
          case Some(ApiGameResult.Draw(_))                               => ("draw", None)
          case None                                                      => computeLiveStatus(snapshot)

  private def computeLiveStatus(snapshot: GameSnapshot): (String, Option[String]) =
    val ctx = snapshot.context
    if DefaultRules.isCheck(ctx) then ("check", None)
    else if snapshot.drawOfferedBy.isDefined then ("drawOffered", None)
    else if DefaultRules.isFiftyMoveRule(ctx) then ("fiftyMoveAvailable", None)
    else ("started", None)

  def moveToUci(move: Move): String =
    val base = s"${move.from}${move.to}"
    move.moveType match
      case MoveType.Promotion(piece) =>
        val suffix = piece match
          case PromotionPiece.Queen  => "q"
          case PromotionPiece.Rook   => "r"
          case PromotionPiece.Bishop => "b"
          case PromotionPiece.Knight => "n"
        base + suffix
      case _ => base

  private def buildPgn(moves: List[Move]): String =
    PgnExporter.exportGame(Map.empty, moves)
