package de.nowchess.backcore.game

import de.nowchess.api.board.{Color, File, Rank, Square}
import de.nowchess.api.game.{DrawReason, GameContext, GameResult as ApiGameResult}
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
import de.nowchess.api.player.{PlayerId, PlayerInfo}
import de.nowchess.io.fen.FenParser
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test

class GameMapperTest:

  private val white = PlayerInfo(PlayerId("white"), "White")
  private val black = PlayerInfo(PlayerId("black"), "Black")

  private def snap(
      ctx: GameContext = GameContext.initial,
      externalResult: Option[GameResult] = None,
      drawOfferedBy: Option[Color] = None,
      canUndo: Boolean = false,
      canRedo: Boolean = false,
  ): GameSnapshot =
    GameSnapshot(
      gameId = "testId1",
      white = white,
      black = black,
      context = ctx,
      drawOfferedBy = drawOfferedBy,
      externalResult = externalResult,
      canUndo = canUndo,
      canRedo = canRedo,
    )

  @Test
  def resignWhiteReturnsResignWithWhiteWinner(): Unit =
    val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.Resign(Color.White))))
    assertEquals("resign", state.status)
    assertEquals(Some("white"), state.winner)

  @Test
  def resignBlackReturnsResignWithBlackWinner(): Unit =
    val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.Resign(Color.Black))))
    assertEquals("resign", state.status)
    assertEquals(Some("black"), state.winner)

  @Test
  def agreedDrawReturnsDrawNoWinner(): Unit =
    val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.AgreedDraw)))
    assertEquals("draw", state.status)
    assertEquals(None, state.winner)

  @Test
  def fiftyMoveDrawReturnsDrawNoWinner(): Unit =
    val state = GameMapper.toGameState(snap(externalResult = Some(GameResult.FiftyMoveDraw)))
    assertEquals("draw", state.status)
    assertEquals(None, state.winner)

  @Test
  def contextWinWhiteReturnsCheckmate(): Unit =
    val ctx   = GameContext.initial.withResult(Some(ApiGameResult.Win(Color.White)))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("checkmate", state.status)
    assertEquals(Some("white"), state.winner)

  @Test
  def contextWinBlackReturnsCheckmate(): Unit =
    val ctx   = GameContext.initial.withResult(Some(ApiGameResult.Win(Color.Black)))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("checkmate", state.status)
    assertEquals(Some("black"), state.winner)

  @Test
  def contextDrawStalemateReturnsStalemate(): Unit =
    val ctx   = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.Stalemate)))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("stalemate", state.status)
    assertEquals(None, state.winner)

  @Test
  def contextDrawInsufficientMaterialReturnsInsufficientMaterial(): Unit =
    val ctx   = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.InsufficientMaterial)))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("insufficientMaterial", state.status)
    assertEquals(None, state.winner)

  @Test
  def contextDrawFiftyMoveRuleReturnsDraw(): Unit =
    val ctx   = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.FiftyMoveRule)))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("draw", state.status)
    assertEquals(None, state.winner)

  @Test
  def contextDrawAgreementReturnsDraw(): Unit =
    val ctx   = GameContext.initial.withResult(Some(ApiGameResult.Draw(DrawReason.Agreement)))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("draw", state.status)
    assertEquals(None, state.winner)

  @Test
  def liveCheckPositionReturnsCheckStatus(): Unit =
    // White king on a1, black rook on h1 — white is in check
    val ctx = FenParser.parseFen("k7/8/8/8/8/8/8/K6r w - - 0 1")
      .getOrElse(fail("Invalid FEN"))
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("check", state.status)

  @Test
  def liveDrawOfferedBySetReturnsDrawOffered(): Unit =
    val state = GameMapper.toGameState(snap(drawOfferedBy = Some(Color.White)))
    assertEquals("drawOffered", state.status)

  @Test
  def liveFiftyMoveClockGe100ReturnsFiftyMoveAvailable(): Unit =
    val ctx   = GameContext.initial.withHalfMoveClock(100)
    val state = GameMapper.toGameState(snap(ctx = ctx))
    assertEquals("fiftyMoveAvailable", state.status)

  @Test
  def liveNormalPositionReturnsStarted(): Unit =
    val state = GameMapper.toGameState(snap())
    assertEquals("started", state.status)
    assertEquals(None, state.winner)

  @Test
  def canUndoReflectedInToGameState(): Unit =
    val state = GameMapper.toGameState(snap(canUndo = true, canRedo = false))
    assertTrue(state.undoAvailable)
    assertFalse(state.redoAvailable)

  @Test
  def canRedoReflectedInToGameState(): Unit =
    val state = GameMapper.toGameState(snap(canUndo = false, canRedo = true))
    assertFalse(state.undoAvailable)
    assertTrue(state.redoAvailable)

  @Test
  def moveToUciNormalQuiet(): Unit =
    val sq   = Square(File.E, Rank.R2)
    val sq2  = Square(File.E, Rank.R4)
    val move = Move(sq, sq2, MoveType.Normal(false))
    assertEquals("e2e4", GameMapper.moveToUci(move))

  @Test
  def moveToUciNormalCapture(): Unit =
    val move = Move(Square(File.D, Rank.R4), Square(File.E, Rank.R5), MoveType.Normal(true))
    assertEquals("d4e5", GameMapper.moveToUci(move))

  @Test
  def moveToUciCastleKingside(): Unit =
    val move = Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside)
    assertEquals("e1g1", GameMapper.moveToUci(move))

  @Test
  def moveToUciCastleQueenside(): Unit =
    val move = Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside)
    assertEquals("e1c1", GameMapper.moveToUci(move))

  @Test
  def moveToUciEnPassant(): Unit =
    val move = Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant)
    assertEquals("e5d6", GameMapper.moveToUci(move))

  @Test
  def moveToUciPromotionQueen(): Unit =
    val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Queen))
    assertEquals("e7e8q", GameMapper.moveToUci(move))

  @Test
  def moveToUciPromotionRook(): Unit =
    val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Rook))
    assertEquals("e7e8r", GameMapper.moveToUci(move))

  @Test
  def moveToUciPromotionBishop(): Unit =
    val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Bishop))
    assertEquals("e7e8b", GameMapper.moveToUci(move))

  @Test
  def moveToUciPromotionKnight(): Unit =
    val move = Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Knight))
    assertEquals("e7e8n", GameMapper.moveToUci(move))

  @Test
  def toGameFullPopulatesAllFields(): Unit =
    val full = GameMapper.toGameFull(snap())
    assertEquals("testId1", full.gameId)
    assertEquals("white", full.white.id)
    assertEquals("black", full.black.id)
    assertNotNull(full.state)
