using JoshHeaps.Net.Models;
using JoshHeaps.Net.Services.Interfaces;

namespace JoshHeaps.Net.Services.Implementations;

public class ChessService : IChessService
{
    public GameState CreateNewGame()
    {
        var gameState = new GameState();

        InitializeBoard(gameState);

        return gameState;
    }

    private void InitializeBoard(GameState gameState)
    {
        for (int r = 0; r < 8; r++)
            for (int c = 0; c < 8; c++)
                gameState.Board[r, c] = null;

        gameState.Pieces.Clear();
        gameState.MoveHistory.Clear();

        gameState.WhiteCanCastleKingside = true;
        gameState.WhiteCanCastleQueenside = true;
        gameState.BlackCanCastleKingside = true;
        gameState.BlackCanCastleQueenside = true;
        gameState.EnPassantTarget = null;

        SetupBlackPieces(gameState);
        SetupWhitePieces(gameState);

        gameState.CurrentPlayer = PieceColor.White;

        UpdateCheckStatus(gameState);
    }

    private static void SetupBlackPieces(GameState gs)
    {
        var blackMajors = new[]
        {
            PieceType.Rook, PieceType.Knight, PieceType.Bishop,
            PieceType.Queen, PieceType.King, PieceType.Bishop,
            PieceType.Knight, PieceType.Rook
        };

        for (int c = 0; c < 8; c++)
        {
            var major = new ChessPiece($"b{blackMajors[c]}{c}", blackMajors[c], PieceColor.Black, new Position(0, c));
            gs.Board[0, c] = major;
            gs.Pieces.Add(major);
        }

        for (int c = 0; c < 8; c++)
        {
            var pawn = new ChessPiece($"bPawn{c}", PieceType.Pawn, PieceColor.Black, new Position(1, c));
            gs.Board[1, c] = pawn;
            gs.Pieces.Add(pawn);
        }
    }

    private static void SetupWhitePieces(GameState gs)
    {
        var whiteMajors = new[]
        {
            PieceType.Rook, PieceType.Knight, PieceType.Bishop,
            PieceType.Queen, PieceType.King, PieceType.Bishop,
            PieceType.Knight, PieceType.Rook
        };

        for (int c = 0; c < 8; c++)
        {
            var major = new ChessPiece($"w{whiteMajors[c]}{c}", whiteMajors[c], PieceColor.White, new Position(7, c));
            gs.Board[7, c] = major;
            gs.Pieces.Add(major);
        }

        for (int c = 0; c < 8; c++)
        {
            var pawn = new ChessPiece($"wPawn{c}", PieceType.Pawn, PieceColor.White, new Position(6, c));
            gs.Board[6, c] = pawn;
            gs.Pieces.Add(pawn);
        }
    }

    public List<(ChessPiece piece, List<Position> moves)> GetAllLegalMoves(GameState gameState)
    {
        var result = new List<(ChessPiece piece, List<Position> moves)>();

        var currentPieces = gameState
            .Pieces
            .Where(p => p.Color == gameState.CurrentPlayer && p.Position.Row >= 0)
            .ToList();

        foreach (var piece in currentPieces)
        {
            var moves = GetLegalMovesForPiece(gameState, piece.Id);

            if (moves.Count > 0)
                result.Add((piece, moves));
        }

        return result;
    }

    public List<Position> GetLegalMovesForPiece(GameState gameState, string pieceId)
    {
        var piece = gameState.Pieces.FirstOrDefault(p => p.Id == pieceId);

        if (piece == null) return [];
        if (piece.Color != gameState.CurrentPlayer) return [];

        var candidateMoves = GenerateCandidateMoves(gameState, piece);
        var legalMoves = new List<Position>();

        foreach (var pos in candidateMoves)
            if (IsMoveLegalConsideringCheck(gameState, piece, pos))
                legalMoves.Add(pos);

        return legalMoves;
    }

    public MoveResultDto MakeMove(GameState gameState, MoveDto moveDto)
    {
        var piece = gameState.Pieces.FirstOrDefault(p => p.Id == moveDto.PieceId);

        if (piece == null)
            return new MoveResultDto { Success = false, Message = "Piece not found." };

        if (piece.Color != gameState.CurrentPlayer)
            return new MoveResultDto { Success = false, Message = "Not your turn." };

        var legalMoves = GetLegalMovesForPiece(gameState, piece.Id);
        var targetPos = new Position(moveDto.TargetRow, moveDto.TargetCol);

        if (!legalMoves.Any(m => m.Row == moveDto.TargetRow && m.Col == moveDto.TargetCol))
            return new MoveResultDto { Success = false, Message = "Illegal move." };

        PerformMove(gameState, piece, targetPos, moveDto);

        UpdateCheckStatus(gameState);

        var notation = $"{piece.Id}:{piece.Position}->{targetPos}";
        gameState.MoveHistory.Add(notation);

        return new MoveResultDto
        {
            Success = true,
            Message = "Move successful.",
            IsCheck = gameState.IsCheck,
            IsCheckmate = gameState.IsCheckmate,
            IsStalemate = gameState.IsStalemate
        };
    }

    private static void PerformMove(GameState gs, ChessPiece piece, Position targetPos, MoveDto moveDto)
    {
        var oldPos = piece.Position;
        var captured = gs.Board[targetPos.Row, targetPos.Col];

        HandleEnPassantIfNeeded(gs, piece, targetPos, ref captured);

        gs.Board[oldPos.Row, oldPos.Col] = null;
        piece.Position = targetPos;
        gs.Board[targetPos.Row, targetPos.Col] = piece;

        if (captured != null && captured != piece)
            captured.Position = new Position(-1, -1);

        bool wasFirstMove = !piece.HasMoved;
        piece.HasMoved = true;

        HandleCastlingIfNeeded(gs, piece, oldPos, targetPos, wasFirstMove);

        HandlePawnTwoSquareMove(gs, piece, oldPos, targetPos, wasFirstMove);

        HandlePawnPromotionIfNeeded(piece, moveDto);

        UpdateCastlingRights(gs, piece, oldPos);

        gs.CurrentPlayer = gs.CurrentPlayer == PieceColor.White
            ? PieceColor.Black
            : PieceColor.White;
    }

    private static void HandleEnPassantIfNeeded(GameState gs, ChessPiece piece, Position targetPos, ref ChessPiece? captured)
    {
        if (piece.Type != PieceType.Pawn || !gs.EnPassantTarget.HasValue)
            return;

        var enPassantSquare = gs.EnPassantTarget.Value;

        if (targetPos.Row == enPassantSquare.Row && targetPos.Col == enPassantSquare.Col)
        {
            int direction = piece.Color == PieceColor.White ? 1 : -1;
            var capturedPos = new Position(targetPos.Row + direction, targetPos.Col);
            var potentialPawn = gs.Board[capturedPos.Row, capturedPos.Col];

            if (potentialPawn != null && potentialPawn.Color != piece.Color && potentialPawn.Type == PieceType.Pawn)
            {
                gs.Board[capturedPos.Row, capturedPos.Col] = null;
                potentialPawn.Position = new Position(-1, -1);
                captured = potentialPawn;
            }
        }
    }

    private static void HandleCastlingIfNeeded(GameState gs, ChessPiece piece, Position oldPos, Position targetPos, bool wasFirstMove)
    {
        if (piece.Type != PieceType.King || !wasFirstMove)
            return;

        var colDiff = targetPos.Col - oldPos.Col;

        if (Math.Abs(colDiff) == 2)
        {
            bool isKingside = colDiff > 0;
            int rookStartCol = isKingside ? 7 : 0;
            int rookEndCol = isKingside ? 5 : 3;
            var rook = gs.Board[oldPos.Row, rookStartCol];

            if (rook != null && rook.Type == PieceType.Rook && !rook.HasMoved)
            {
                gs.Board[oldPos.Row, rookStartCol] = null;
                rook.Position = new Position(oldPos.Row, rookEndCol);
                gs.Board[oldPos.Row, rookEndCol] = rook;
                rook.HasMoved = true;
            }
        }
    }

    private static void HandlePawnTwoSquareMove(GameState gs, ChessPiece piece, Position oldPos, Position targetPos, bool wasFirstMove)
    {
        gs.EnPassantTarget = null;

        if (piece.Type != PieceType.Pawn || !wasFirstMove)
            return;

        var rowDiff = Math.Abs(targetPos.Row - oldPos.Row);

        if (rowDiff == 2)
        {
            var rowBehind = (oldPos.Row + targetPos.Row) / 2;
            gs.EnPassantTarget = new Position(rowBehind, oldPos.Col);
        }
    }

    private static void HandlePawnPromotionIfNeeded(ChessPiece piece, MoveDto moveDto)
    {
        if (piece.Type != PieceType.Pawn)
            return;

        bool promotionRow = piece.Color == PieceColor.White
            ? piece.Position.Row == 0
            : piece.Position.Row == 7;

        if (!promotionRow) return;

        var promotionChoice = moveDto.PromotionChoice ?? PieceType.Queen;
        piece.Type = promotionChoice;
    }

    private static void UpdateCastlingRights(GameState gs, ChessPiece movedPiece, Position oldPos)
    {
        if (movedPiece.Type == PieceType.King)
        {
            if (movedPiece.Color == PieceColor.White)
            {
                gs.WhiteCanCastleKingside = false;
                gs.WhiteCanCastleQueenside = false;
            }
            else
            {
                gs.BlackCanCastleKingside = false;
                gs.BlackCanCastleQueenside = false;
            }

            return;
        }

        if (movedPiece.Type == PieceType.Rook)
        {
            if (movedPiece.Color == PieceColor.White)
            {
                if (oldPos.Row == 7 && oldPos.Col == 0)
                    gs.WhiteCanCastleQueenside = false;

                if (oldPos.Row == 7 && oldPos.Col == 7)
                    gs.WhiteCanCastleKingside = false;
            }
            else
            {
                if (oldPos.Row == 0 && oldPos.Col == 0)
                    gs.BlackCanCastleQueenside = false;

                if (oldPos.Row == 0 && oldPos.Col == 7)
                    gs.BlackCanCastleKingside = false;
            }
        }
    }

    private IEnumerable<Position> GenerateCandidateMoves(GameState gs, ChessPiece piece)
    {
        return piece.Type switch
        {
            PieceType.Pawn => GeneratePawnMoves(gs, piece),
            PieceType.Rook => GenerateRookMoves(gs, piece),
            PieceType.Knight => GenerateKnightMoves(gs, piece),
            PieceType.Bishop => GenerateBishopMoves(gs, piece),
            PieceType.Queen => GenerateQueenMoves(gs, piece),
            PieceType.King => GenerateKingMoves(gs, piece),
            _ => []
        };
    }

    private bool IsMoveLegalConsideringCheck(GameState gs, ChessPiece piece, Position target)
    {
        var clone = CloneGameState(gs);
        var clonedPiece = clone.Pieces.First(p => p.Id == piece.Id);
        var oldPos = clonedPiece.Position;

        clone.Board[oldPos.Row, oldPos.Col] = null;

        var captured = clone.Board[target.Row, target.Col];

        if (captured != null) captured.Position = new Position(-1, -1);

        clonedPiece.Position = target;
        clone.Board[target.Row, target.Col] = clonedPiece;

        HandleEnPassantOnClone(clone, clonedPiece, target);

        HandleCastlingOnClone(clone, clonedPiece, oldPos, target);

        var myKing = clone
            .Pieces
            .FirstOrDefault(p => p.Color == piece.Color
                                 && p.Type == PieceType.King
                                 && p.Position.Row >= 0);

        if (myKing == null) return false;

        bool inCheck = IsSquareAttacked(clone, myKing.Position, myKing.Color);

        return !inCheck;
    }

    private static void HandleEnPassantOnClone(GameState clone, ChessPiece clonedPiece, Position target)
    {
        if (clonedPiece.Type != PieceType.Pawn || !clone.EnPassantTarget.HasValue)
            return;

        if (target.Row == clone.EnPassantTarget.Value.Row && target.Col == clone.EnPassantTarget.Value.Col)
        {
            int direction = clonedPiece.Color == PieceColor.White ? 1 : -1;
            var capturedPos = new Position(target.Row + direction, target.Col);
            var epCaptured = clone.Board[capturedPos.Row, capturedPos.Col];

            if (epCaptured != null && epCaptured.Color != clonedPiece.Color && epCaptured.Type == PieceType.Pawn)
            {
                clone.Board[capturedPos.Row, capturedPos.Col] = null;
                epCaptured.Position = new Position(-1, -1);
            }
        }
    }

    private static void HandleCastlingOnClone(GameState clone, ChessPiece clonedPiece, Position oldPos, Position target)
    {
        if (clonedPiece.Type != PieceType.King || clonedPiece.HasMoved)
            return;

        int colDiff = target.Col - oldPos.Col;

        if (Math.Abs(colDiff) == 2)
        {
            bool isKingside = colDiff > 0;
            int rookStartCol = isKingside ? 7 : 0;
            int rookEndCol = isKingside ? 5 : 3;

            var rook = clone.Board[oldPos.Row, rookStartCol];

            if (rook != null && rook.Type == PieceType.Rook && !rook.HasMoved)
            {
                clone.Board[oldPos.Row, rookStartCol] = null;
                rook.Position = new Position(oldPos.Row, rookEndCol);
                clone.Board[oldPos.Row, rookEndCol] = rook;
            }
        }
    }

    private bool IsSquareAttacked(GameState gs, Position square, PieceColor colorOfSquare)
    {
        var enemyColor = colorOfSquare == PieceColor.White ? PieceColor.Black : PieceColor.White;

        var enemyPieces = gs
            .Pieces
            .Where(p => p.Color == enemyColor && p.Position.Row >= 0)
            .ToList();

        foreach (var enemy in enemyPieces)
        {
            var moves = GenerateCandidateMoves(gs, enemy);

            if (moves.Any(m => m.Row == square.Row && m.Col == square.Col))
                return true;
        }

        return false;
    }

    private void UpdateCheckStatus(GameState gs)
    {
        gs.IsCheck = false;
        gs.IsCheckmate = false;
        gs.IsStalemate = false;

        var king = gs
            .Pieces
            .FirstOrDefault(p => p.Color == gs.CurrentPlayer
                                 && p.Type == PieceType.King
                                 && p.Position.Row >= 0);

        // If there's no king, that's effectively checkmate for that side.
        if (king == null)
        {
            gs.IsCheck = true;
            gs.IsCheckmate = true;
            return;
        }

        bool inCheck = IsSquareAttacked(gs, king.Position, gs.CurrentPlayer);

        gs.IsCheck = inCheck;

        var allMoves = GetAllLegalMoves(gs);

        if (allMoves.Count == 0 && inCheck)
        {
            gs.IsCheckmate = true;

            return;
        }

        if (allMoves.Count == 0 && !inCheck)
        {
            gs.IsStalemate = true;

            return;
        }
    }

    private static GameState CloneGameState(GameState original)
    {
        var clone = new GameState
        {
            GameId = original.GameId,
            CurrentPlayer = original.CurrentPlayer,
            EnPassantTarget = original.EnPassantTarget,
            WhiteCanCastleKingside = original.WhiteCanCastleKingside,
            WhiteCanCastleQueenside = original.WhiteCanCastleQueenside,
            BlackCanCastleKingside = original.BlackCanCastleKingside,
            BlackCanCastleQueenside = original.BlackCanCastleQueenside,
            MoveHistory = new List<string>(original.MoveHistory),
            Board = new ChessPiece[8,8],
        };

        foreach (var p in original.Pieces)
        {
            var copy = new ChessPiece(p.Id, p.Type, p.Color, p.Position)
            {
                HasMoved = p.HasMoved
            };

            clone.Pieces.Add(copy);
        }

        foreach (var cp in clone.Pieces)
            if (cp.Position.Row >= 0)
                clone.Board[cp.Position.Row, cp.Position.Col] = cp;

        return clone;
    }

    private static List<Position> GeneratePawnMoves(GameState gs, ChessPiece piece)
    {
        var moves = new List<Position>();
        int direction = piece.Color == PieceColor.White ? -1 : 1;
        var startRow = piece.Position.Row;
        var startCol = piece.Position.Col;

        var forward1 = startRow + direction;

        if (IsOnBoard(forward1, startCol) && gs.Board[forward1, startCol] == null)
        {
            moves.Add(new Position(forward1, startCol));

            if (!piece.HasMoved)
            {
                var forward2 = startRow + 2 * direction;

                if (IsOnBoard(forward2, startCol) && gs.Board[forward2, startCol] == null)
                    moves.Add(new Position(forward2, startCol));
            }
        }

        var leftCol = startCol - 1;
        var rightCol = startCol + 1;

        if (IsOnBoard(forward1, leftCol))
        {
            var occupant = gs.Board[forward1, leftCol];

            if (occupant != null && occupant.Color != piece.Color)
                moves.Add(new Position(forward1, leftCol));
        }

        if (IsOnBoard(forward1, rightCol))
        {
            var occupant = gs.Board[forward1, rightCol];

            if (occupant != null && occupant.Color != piece.Color)
                moves.Add(new Position(forward1, rightCol));
        }

        if (gs.EnPassantTarget.HasValue)
        {
            var ep = gs.EnPassantTarget.Value;

            if (ep.Row == forward1 && Math.Abs(ep.Col - startCol) == 1)
                moves.Add(ep);
        }

        return moves;
    }

    private static List<Position> GenerateRookMoves(GameState gs, ChessPiece piece)
        => GenerateSlidingMoves(gs, piece, [(1, 0), (-1, 0), (0, 1), (0, -1)]);

    private static List<Position> GenerateBishopMoves(GameState gs, ChessPiece piece)
        => GenerateSlidingMoves(gs, piece, [(1, 1), (1, -1), (-1, 1), (-1, -1)]);

    private static List<Position> GenerateQueenMoves(GameState gs, ChessPiece piece)
        => GenerateSlidingMoves(gs, piece,
        [
            (1, 0), (-1, 0), (0, 1), (0, -1),
            (1, 1), (1, -1), (-1, 1), (-1, -1)
        ]);

    private static List<Position> GenerateSlidingMoves(GameState gs, ChessPiece piece, (int dr, int dc)[] directions)
    {
        var results = new List<Position>();
        var (startRow, startCol) = piece.Position;

        foreach (var (dr, dc) in directions)
            results.AddRange(GetSlidingMovesInDirection(gs, piece, startRow, startCol, dr, dc));

        return results;
    }

    private static List<Position> GetSlidingMovesInDirection(
        GameState gs,
        ChessPiece piece,
        int row,
        int col,
        int dr,
        int dc)
    {
        var moves = new List<Position>();
        var r = row;
        var c = col;

        while (true)
        {
            r += dr;
            c += dc;

            if (!IsOnBoard(r, c))
                return moves;

            var occupant = gs.Board[r, c];

            if (occupant == null)
            {
                moves.Add(new Position(r, c));
                continue;
            }

            if (occupant.Color != piece.Color)
                moves.Add(new Position(r, c));

            return moves;
        }
    }

    private static IEnumerable<Position> GenerateKnightMoves(GameState gs, ChessPiece piece)
    {
        var offsets = new (int, int)[]
        {
            (2,1), (2,-1), (-2,1), (-2,-1),
            (1,2), (1,-2), (-1,2), (-1,-2)
        };

        foreach (var (dr, dc) in offsets)
        {
            var r = piece.Position.Row + dr;
            var c = piece.Position.Col + dc;

            if (!IsOnBoard(r, c))
                continue;

            var occupant = gs.Board[r, c];

            if (occupant == null || occupant.Color != piece.Color)
                yield return new Position(r, c);
        }
    }

    private List<Position> GenerateKingMoves(GameState gs, ChessPiece piece)
    {
        var results = new List<Position>();
        var offsets = new[]
        {
            (1,0), (-1,0), (0,1), (0,-1),
            (1,1), (1,-1), (-1,1), (-1,-1)
        };

        foreach (var (dr, dc) in offsets)
        {
            int r = piece.Position.Row + dr;
            int c = piece.Position.Col + dc;

            if (!IsOnBoard(r, c))
                continue;

            var occupant = gs.Board[r, c];

            if (occupant == null || occupant.Color != piece.Color)
                results.Add(new Position(r, c));
        }

        if (!piece.HasMoved && !gs.IsCheck && piece.Color == gs.CurrentPlayer)
            AddCastlingMoves(gs, piece, results);

        return results;
    }

    private void AddCastlingMoves(GameState gs, ChessPiece king, List<Position> results)
    {
        bool hasKingsideRight = king.Color == PieceColor.White
            ? gs.WhiteCanCastleKingside
            : gs.BlackCanCastleKingside;

        bool hasQueensideRight = king.Color == PieceColor.White
            ? gs.WhiteCanCastleQueenside
            : gs.BlackCanCastleQueenside;

        var row = king.Position.Row;
        var col = king.Position.Col;

        bool areKingsideCastleSpacesEmpty = IsEmpty(row, col + 1, gs)
            && IsEmpty(row, col + 2, gs);

        bool isKingsideCastleSafe = !IsSquareAttacked(gs, new Position(row, col + 1), king.Color)
            && !IsSquareAttacked(gs, new Position(row, col + 2), king.Color);

        bool areQueensideCastleSpacesEmpty = IsEmpty(row, col - 1, gs)
            && IsEmpty(row, col - 2, gs)
            && IsEmpty(row, col - 3, gs);

        bool isQueensideCastleSafe = !IsSquareAttacked(gs, new Position(row, col - 1), king.Color)
            && !IsSquareAttacked(gs, new Position(row, col - 2), king.Color);

        bool canCastleKingside = areKingsideCastleSpacesEmpty
            && isKingsideCastleSafe
            && hasKingsideRight;

        bool canCastleQueenside = areQueensideCastleSpacesEmpty
            && isQueensideCastleSafe
            && hasQueensideRight;

        if (canCastleKingside) results.Add(new Position(row, col + 2));

        if (canCastleQueenside) results.Add(new Position(row, col - 2));
    }

    private static bool IsEmpty(int r, int c, GameState gs)
    {
        if (!IsOnBoard(r, c)) return false;

        return gs.Board[r, c] == null;
    }

    private static bool IsOnBoard(int r, int c)
        => r >= 0 && r < 8 && c >= 0 && c < 8;
}
