package de.szut.casino.blackjack;

import de.szut.casino.user.UserEntity;
import de.szut.casino.user.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Random;

@Service
public class BlackJackService {
    private final BlackJackGameRepository blackJackGameRepository;
    private final UserRepository userRepository;
    private final Random random = new Random();

    public BlackJackService(BlackJackGameRepository blackJackGameRepository, UserRepository userRepository) {
        this.blackJackGameRepository = blackJackGameRepository;
        this.userRepository = userRepository;
    }

    public BlackJackGameEntity getBlackJackGame(Long id) {
        return blackJackGameRepository.findById(id).orElse(null);
    }

    @Transactional
    public BlackJackGameEntity createBlackJackGame(UserEntity user, BigDecimal betAmount) {
        BlackJackGameEntity game = new BlackJackGameEntity();
        game.setUser(user);
        game.setBet(betAmount);
        
        initializeDeck(game);
        dealInitialCards(game);
        
        game.setState(getState(game));
        deductBetFromBalance(user, betAmount);
        
        return blackJackGameRepository.save(game);
    }

    @Transactional
    public BlackJackGameEntity hit(BlackJackGameEntity game) {
        if (game.getState() != BlackJackState.IN_PROGRESS) {
            return game;
        }
        
        dealCardToPlayer(game);
        
        updateGameStateAndBalance(game);
        
        return blackJackGameRepository.save(game);
    }

    @Transactional
    public BlackJackGameEntity stand(BlackJackGameEntity game) {
        if (game.getState() != BlackJackState.IN_PROGRESS) {
            return game;
        }
        
        dealCardsToDealerUntilMinimumScore(game);
        determineWinnerAndUpdateBalance(game);
        
        return blackJackGameRepository.save(game);
    }

    @Transactional
    public BlackJackGameEntity doubleDown(BlackJackGameEntity game) {
        if (game.getState() != BlackJackState.IN_PROGRESS || game.getPlayerCards().size() != 2) {
            return game;
        }
        
        UserEntity user = getUserWithFreshData(game.getUser());
        BigDecimal additionalBet = game.getBet();
        
        deductBetFromBalance(user, additionalBet);
        game.setBet(game.getBet().add(additionalBet));
        
        dealCardToPlayer(game);
        updateGameStateAndBalance(game);
        
        if (game.getState() == BlackJackState.IN_PROGRESS) {
            return stand(game);
        }
        
        return game;
    }

    @Transactional
    public BlackJackGameEntity split(BlackJackGameEntity game) {
        if (game.getState() != BlackJackState.IN_PROGRESS || 
            game.getPlayerCards().size() != 2 || 
            game.isSplit() ||
            !game.getPlayerCards().get(0).getRank().equals(game.getPlayerCards().get(1).getRank())) {
            return game;
        }
        
        UserEntity user = getUserWithFreshData(game.getUser());
        BigDecimal splitBet = game.getBet();
        
        if (user.getBalance().compareTo(splitBet) < 0) {
            return game;
        }
        
        deductBetFromBalance(user, splitBet);
        game.setSplitBet(splitBet);
        game.setSplit(true);
        
        CardEntity card = game.getPlayerCards().remove(1);
        card.setCardType(CardType.PLAYER_SPLIT);
        game.getPlayerSplitCards().add(card);
        
        dealCardToPlayer(game);
        dealCardToSplitHand(game);
        
        return blackJackGameRepository.save(game);
    }

    private BlackJackGameEntity refreshGameState(BlackJackGameEntity game) {
        return blackJackGameRepository.findById(game.getId()).orElse(game);
    }
    
    private UserEntity getUserWithFreshData(UserEntity user) {
        return userRepository.findById(user.getId()).orElse(user);
    }
    
    private void dealInitialCards(BlackJackGameEntity game) {
        for (int i = 0; i < 2; i++) {
            dealCardToPlayer(game);
        }
        
        dealCardToDealer(game);
    }
    
    private void dealCardToPlayer(BlackJackGameEntity game) {
        CardEntity card = drawCardFromDeck(game);
        card.setCardType(CardType.PLAYER);
        game.getPlayerCards().add(card);
    }
    
    private void dealCardToDealer(BlackJackGameEntity game) {
        CardEntity card = drawCardFromDeck(game);
        card.setCardType(CardType.DEALER);
        game.getDealerCards().add(card);
    }
    
    private void dealCardsToDealerUntilMinimumScore(BlackJackGameEntity game) {
        while (calculateHandValue(game.getDealerCards()) < 17) {
            dealCardToDealer(game);
        }
    }
    
    private void dealCardToSplitHand(BlackJackGameEntity game) {
        CardEntity card = drawCardFromDeck(game);
        card.setCardType(CardType.PLAYER_SPLIT);
        game.getPlayerSplitCards().add(card);
    }
    
    private void updateGameStateAndBalance(BlackJackGameEntity game) {
        if (game.isSplit()) {
            int mainHandValue = calculateHandValue(game.getPlayerCards());
            int splitHandValue = calculateHandValue(game.getPlayerSplitCards());
            
            if (mainHandValue > 21 && splitHandValue > 21) {
                game.setState(BlackJackState.PLAYER_LOST);
                updateUserBalance(game, false);
            } else if (mainHandValue <= 21 && splitHandValue <= 21) {
                game.setState(BlackJackState.IN_PROGRESS);
            } else {
                game.setState(BlackJackState.IN_PROGRESS);
            }
        } else {
            game.setState(getState(game));
            
            if (game.getState() == BlackJackState.PLAYER_WON) {
                updateUserBalance(game, true);
            } else if (game.getState() == BlackJackState.PLAYER_LOST) {
                updateUserBalance(game, false);
            }
        }
    }
    
    private void determineWinnerAndUpdateBalance(BlackJackGameEntity game) {
        int playerValue = calculateHandValue(game.getPlayerCards());
        int dealerValue = calculateHandValue(game.getDealerCards());

        if (dealerValue > 21 || playerValue > dealerValue) {
            game.setState(BlackJackState.PLAYER_WON);
            updateUserBalance(game, true);
        } else if (playerValue < dealerValue) {
            game.setState(BlackJackState.PLAYER_LOST);
            updateUserBalance(game, false);
        } else {
            game.setState(BlackJackState.DRAW);
            updateUserBalance(game, false);
        }
    }
    
    private void deductBetFromBalance(UserEntity user, BigDecimal betAmount) {
        user.setBalance(user.getBalance().subtract(betAmount));
        userRepository.save(user);
    }

    @Transactional
    private void updateUserBalance(BlackJackGameEntity game, boolean isWin) {
        UserEntity user = getUserWithFreshData(game.getUser());
        BigDecimal totalBet = game.getBet();
        BigDecimal balance = user.getBalance();
        
        if (game.isSplit()) {
            totalBet = totalBet.add(game.getSplitBet());
            
            if (isWin) {
                int mainHandValue = calculateHandValue(game.getPlayerCards());
                int splitHandValue = calculateHandValue(game.getPlayerSplitCards());
                int dealerValue = calculateHandValue(game.getDealerCards());
                
                if (mainHandValue <= 21 && (dealerValue > 21 || mainHandValue > dealerValue)) {
                    balance = balance.add(game.getBet().multiply(BigDecimal.valueOf(2)));
                } else if (mainHandValue == dealerValue) {
                    balance = balance.add(game.getBet());
                }
                
                if (splitHandValue <= 21 && (dealerValue > 21 || splitHandValue > dealerValue)) {
                    balance = balance.add(game.getSplitBet().multiply(BigDecimal.valueOf(2)));
                } else if (splitHandValue == dealerValue) {
                    balance = balance.add(game.getSplitBet());
                }
            } else if (game.getState() == BlackJackState.DRAW) {
                balance = balance.add(totalBet);
            }
        } else {
            if (isWin) {
                balance = balance.add(totalBet.multiply(BigDecimal.valueOf(2)));
            } else if (game.getState() == BlackJackState.DRAW) {
                balance = balance.add(totalBet);
            }
        }
        
        user.setBalance(balance);
        userRepository.save(user);
    }

    private void initializeDeck(BlackJackGameEntity game) {
        for (Suit suit : Suit.values()) {
            for (Rank rank : Rank.values()) {
                CardEntity card = new CardEntity();
                card.setGame(game);
                card.setSuit(suit);
                card.setRank(rank);
                card.setCardType(CardType.DECK);
                game.getDeck().add(card);
            }
        }

        java.util.Collections.shuffle(game.getDeck(), random);
    }

    private CardEntity drawCardFromDeck(BlackJackGameEntity game) {
        if (game.getDeck().isEmpty()) {
            throw new IllegalStateException("Deck is empty");
        }

        return game.getDeck().removeFirst();
    }

    private BlackJackState getState(BlackJackGameEntity game) {
        int playerHandValue = calculateHandValue(game.getPlayerCards());

        if (playerHandValue == 21) {
            CardEntity hole = drawCardFromDeck(game);
            hole.setCardType(CardType.DEALER);
            game.getDealerCards().add(hole);

            int dealerHandValue = calculateHandValue(game.getDealerCards());

            if (dealerHandValue == 21) {
                return BlackJackState.DRAW;
            } else {
                BigDecimal blackjackWinnings = game.getBet().multiply(new BigDecimal("1.5"));
                UserEntity user = getUserWithFreshData(game.getUser());
                user.setBalance(user.getBalance().add(blackjackWinnings));
                return BlackJackState.PLAYER_BLACKJACK;
            }
        } else if (playerHandValue > 21) {
            return BlackJackState.PLAYER_LOST;
        }

        return BlackJackState.IN_PROGRESS;
    }

    private int calculateHandValue(List<CardEntity> hand) {
        int sum = 0;
        int aceCount = 0;
        for (CardEntity card : hand) {
            sum += card.getRank().getValue();
            if (card.getRank() == Rank.ACE) {
                aceCount++;
            }
        }

        while (sum > 21 && aceCount > 0) {
            sum -= 10;
            aceCount--;
        }

        return sum;
    }
}
