package de.nowchess.backcore.game

import de.nowchess.api.board.{Color, File, Rank, Square}
import de.nowchess.backcore.dto.{CreateGameRequest, ImportFenRequest, PlayerInfoDto}
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{BeforeEach, Test}

class GameServiceTest:

  private val promotionFen = "8/4P3/8/8/8/8/8/4K2k w - - 0 1"

  private def freshService(): GameService =
    val svc = new GameService()
    svc.reset(CreateGameRequest())
    svc

  @Test
  def resetClearsStateAndReturnsSnapshot(): Unit =
    val svc = freshService()
    svc.resign()
    val snap = svc.reset(CreateGameRequest())
    assertEquals(GameEngineHolder.gameId, snap.gameId)
    assertEquals(None, snap.externalResult)
    assertEquals(None, snap.drawOfferedBy)

  @Test
  def resetWithPlayersUpdatesHolderInfo(): Unit =
    val svc = freshService()
    val req = CreateGameRequest(
      white = Some(PlayerInfoDto("p1", "Alice")),
      black = Some(PlayerInfoDto("p2", "Bob")),
    )
    val snap = svc.reset(req)
    assertEquals("Alice", snap.white.displayName)
    assertEquals("Bob", snap.black.displayName)

  @Test
  def applyMoveInvalidUciReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.applyMove("zzzz")
    assertTrue(result.isLeft)

  @Test
  def applyMoveIllegalMoveReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.applyMove("e2e5")
    assertTrue(result.isLeft)

  @Test
  def applyMoveValidMoveReturnsRightWithUpdatedContext(): Unit =
    val svc    = freshService()
    val result = svc.applyMove("e2e4")
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertEquals(Color.Black, snap.context.turn)

  @Test
  def applyMoveWhenGameOverReturnsLeft(): Unit =
    val svc = freshService()
    svc.resign()
    val result = svc.applyMove("e2e4")
    assertTrue(result.isLeft)
    assertTrue(result.left.getOrElse("").contains("Game is already over"))

  @Test
  def legalMovesNoneReturnsNonEmptyListAtStart(): Unit =
    val svc   = freshService()
    val moves = svc.legalMoves(None)
    assertFalse(moves.isEmpty)

  @Test
  def legalMovesForE2ReturnsE2e3AndE2e4(): Unit =
    val svc   = freshService()
    val e2    = Square(File.E, Rank.R2)
    val moves = svc.legalMoves(Some(e2))
    val ucis  = moves.map(m => s"${m.from}${m.to}")
    assertTrue(ucis.contains("e2e3"), s"Expected e2e3 in $ucis")
    assertTrue(ucis.contains("e2e4"), s"Expected e2e4 in $ucis")

  @Test
  def undoWithNoHistoryReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.undo()
    assertTrue(result.isLeft)

  @Test
  def undoAfterMoveReverts(): Unit =
    val svc = freshService()
    svc.applyMove("e2e4")
    val result = svc.undo()
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertEquals(Color.White, snap.context.turn)

  @Test
  def redoWithNoRedoStackReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.redo()
    assertTrue(result.isLeft)

  @Test
  def redoAfterUndoReturnsRight(): Unit =
    val svc = freshService()
    svc.applyMove("e2e4")
    svc.undo()
    val result = svc.redo()
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertEquals(Color.Black, snap.context.turn)

  @Test
  def resignNormalReturnsRight(): Unit =
    val svc    = freshService()
    val result = svc.resign()
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertTrue(snap.externalResult.isDefined)
    snap.externalResult match
      case Some(GameResult.Resign(_)) => ()
      case other                      => fail(s"Expected Resign but got $other")

  @Test
  def resignWhenAlreadyResignedReturnsLeft(): Unit =
    val svc = freshService()
    svc.resign()
    val result = svc.resign()
    assertTrue(result.isLeft)

  @Test
  def drawActionOfferSetsDrawOfferedBy(): Unit =
    val svc    = freshService()
    val result = svc.drawAction("offer")
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertTrue(snap.drawOfferedBy.isDefined)

  @Test
  def drawActionAcceptWithNoOfferReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.drawAction("accept")
    assertTrue(result.isLeft)

  @Test
  def drawActionAcceptOwnOfferReturnsLeft(): Unit =
    val svc = freshService()
    svc.drawAction("offer")
    val result = svc.drawAction("accept")
    assertTrue(result.isLeft)
    assertTrue(result.left.getOrElse("").contains("Cannot accept your own draw offer"))

  @Test
  def drawActionAcceptOpponentAcceptsReturnsAgreedDraw(): Unit =
    val svc = freshService()
    svc.drawAction("offer")
    svc.applyMove("e2e4")
    val result = svc.drawAction("accept")
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertEquals(Some(GameResult.AgreedDraw), snap.externalResult)

  @Test
  def drawActionDeclineWithNoOfferReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.drawAction("decline")
    assertTrue(result.isLeft)

  @Test
  def drawActionDeclineAfterOfferClearsOffer(): Unit =
    val svc = freshService()
    svc.drawAction("offer")
    val result = svc.drawAction("decline")
    assertTrue(result.isRight)
    val snap = result.getOrElse(fail("Expected Right"))
    assertEquals(None, snap.drawOfferedBy)

  @Test
  def drawActionClaimWhenFiftyMoveNotTriggeredReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.drawAction("claim")
    assertTrue(result.isLeft)

  @Test
  def drawActionUnknownReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.drawAction("unknown")
    assertTrue(result.isLeft)

  @Test
  def importFenValidReturnsRight(): Unit =
    val svc    = freshService()
    val result = svc.importFen(ImportFenRequest(fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"))
    assertTrue(result.isRight)

  @Test
  def importFenInvalidReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.importFen(ImportFenRequest(fen = "not-a-fen"))
    assertTrue(result.isLeft)

  @Test
  def importPgnValidReturnsRight(): Unit =
    val svc    = freshService()
    val result = svc.importPgn("1. e4 e5 2. Nf3 Nc6 *")
    assertTrue(result.isRight)

  @Test
  def importPgnInvalidReturnsLeft(): Unit =
    val svc    = freshService()
    val result = svc.importPgn("1. z9 *")
    assertTrue(result.isLeft)

  @Test
  def exportFenReturnsNonEmptyString(): Unit =
    val svc = freshService()
    val fen = svc.exportFen()
    assertFalse(fen.isEmpty)

  @Test
  def exportPgnReturnsString(): Unit =
    val svc = freshService()
    val pgn = svc.exportPgn()
    assertNotNull(pgn)

  @Test
  def isKnownIdTrueForHolderGameId(): Unit =
    val svc = freshService()
    assertTrue(svc.isKnownId(GameEngineHolder.gameId))

  @Test
  def isKnownIdFalseForOtherId(): Unit =
    val svc = freshService()
    assertFalse(svc.isKnownId("XXXXXXXX"))

  @Test
  def applyMovePromotionQueenProducesQueen(): Unit =
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = promotionFen))
    val result = svc.applyMove("e7e8q")
    assertTrue(result.isRight, s"Expected Right but got $result")
    val snap = result.getOrElse(fail("Expected Right"))
    val e8   = Square(File.E, Rank.R8)
    snap.context.board.pieceAt(e8) match
      case Some(piece) =>
        assertEquals(de.nowchess.api.board.PieceType.Queen, piece.pieceType)
      case None => fail("Expected queen on e8")

  @Test
  def applyMovePromotionRookProducesRook(): Unit =
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = promotionFen))
    val result = svc.applyMove("e7e8r")
    assertTrue(result.isRight, s"Expected Right but got $result")
    val snap = result.getOrElse(fail("Expected Right"))
    val e8   = Square(File.E, Rank.R8)
    snap.context.board.pieceAt(e8) match
      case Some(piece) => assertEquals(de.nowchess.api.board.PieceType.Rook, piece.pieceType)
      case None        => fail("Expected rook on e8")

  @Test
  def applyMovePromotionBishopProducesBishop(): Unit =
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = promotionFen))
    val result = svc.applyMove("e7e8b")
    assertTrue(result.isRight, s"Expected Right but got $result")
    val snap = result.getOrElse(fail("Expected Right"))
    val e8   = Square(File.E, Rank.R8)
    snap.context.board.pieceAt(e8) match
      case Some(piece) => assertEquals(de.nowchess.api.board.PieceType.Bishop, piece.pieceType)
      case None        => fail("Expected bishop on e8")

  @Test
  def applyMovePromotionKnightProducesKnight(): Unit =
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = promotionFen))
    val result = svc.applyMove("e7e8n")
    assertTrue(result.isRight, s"Expected Right but got $result")
    val snap = result.getOrElse(fail("Expected Right"))
    val e8   = Square(File.E, Rank.R8)
    snap.context.board.pieceAt(e8) match
      case Some(piece) => assertEquals(de.nowchess.api.board.PieceType.Knight, piece.pieceType)
      case None        => fail("Expected knight on e8")

  @Test
  def applyMoveWithoutPromotionCharFallsBackToFirstPromotion(): Unit =
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = promotionFen))
    val result = svc.applyMove("e7e8")
    assertTrue(result.isRight, s"Expected Right but got $result")

  @Test
  def applyMoveWithInvalidPromotionCharFallsBackToFirstPromotion(): Unit =
    // 'x' → parsePromotionChar wildcard branch → None → orElse(headOption)
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = promotionFen))
    val result = svc.applyMove("e7e8x")
    assertTrue(result.isRight, s"Expected Right but got $result")

  @Test
  def drawActionClaimWhenFiftyMoveTriggeredReturnsRight(): Unit =
    val svc = freshService()
    svc.importFen(ImportFenRequest(fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 100 51"))
    val result = svc.drawAction("claim")
    assertTrue(result.isRight, s"Expected Right but got $result")
    val snap = result.getOrElse(fail("Expected Right"))
    assertEquals(Some(GameResult.FiftyMoveDraw), snap.externalResult)

  @Test
  def engineSetterPropagatesNewEngine(): Unit =
    val original = GameEngineHolder.engine
    val fresh    = new de.nowchess.chess.engine.GameEngine()
    GameEngineHolder.engine = fresh
    assertEquals(fresh, GameEngineHolder.engine)
    GameEngineHolder.engine = original
