package de.szut.casino.blackjack;

import de.szut.casino.exceptionHandling.exceptions.InsufficientFundsException;
import de.szut.casino.shared.dto.BetDto;
import de.szut.casino.shared.service.BalanceService;
import de.szut.casino.user.UserEntity;
import de.szut.casino.user.UserRepository;
import de.szut.casino.user.UserService;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;

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

@Service
public class BlackJackService {
    private final BlackJackGameRepository blackJackGameRepository;
    private final UserRepository userRepository;
    private final BalanceService balanceService;
    private final UserService userService;
    private final DeckService deckService;

    public BlackJackService(
            BlackJackGameRepository blackJackGameRepository,
            UserRepository userRepository,
            BalanceService balanceService,
            UserService userService,
            DeckService deckService
    ) {
        this.blackJackGameRepository = blackJackGameRepository;
        this.userRepository = userRepository;
        this.balanceService = balanceService;
        this.userService = userService;
        this.deckService = deckService;
    }

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

    @Transactional
    public BlackJackGameEntity createBlackJackGame(BetDto betDto) {
        UserEntity user = userService.getCurrentUser();

        if (!this.balanceService.hasFunds(user, betDto)) {
            throw new InsufficientFundsException();
        }

        this.balanceService.subtractFunds(user, betDto.getBetAmount());

        BlackJackGameEntity game = new BlackJackGameEntity();
        game.setUser(user);
        game.setBet(betDto.getBetAmount());

        this.deckService.initializeDeck(game);
        this.deckService.dealInitialCards(game);

        game.setState(getState(game));

        return processGameBasedOnState(game);
    }

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

        this.deckService.dealCardToPlayer(game);
        updateGameStateAndBalance(game);

        return processGameBasedOnState(game);
    }

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

        dealCardsToDealerUntilMinimumScore(game);
        determineWinnerAndUpdateBalance(game);

        return processGameBasedOnState(game);
    }

    @Transactional
    public BlackJackGameEntity doubleDown(BlackJackGameEntity game) {
        if (game.getState() != BlackJackState.IN_PROGRESS || game.getPlayerCards().size() != 2) {
            return game;
        }

        UserEntity user = game.getUser();
        BigDecimal additionalBet = game.getBet();

        this.balanceService.subtractFunds(user, additionalBet);

        game.setBet(game.getBet().add(additionalBet));

        this.deckService.dealCardToPlayer(game);
        updateGameStateAndBalance(game);

        if (game.getState() == BlackJackState.IN_PROGRESS) {
            return stand(game);
        }

        return game;
    }

    private BlackJackGameEntity processGameBasedOnState(BlackJackGameEntity game) {
        if (game.getState() != BlackJackState.IN_PROGRESS) {
            this.blackJackGameRepository.delete(game);
            return game;
        }

        return blackJackGameRepository.save(game);
    }

    private void updateGameStateAndBalance(BlackJackGameEntity game) {
        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);
        }
    }

    protected void updateUserBalance(BlackJackGameEntity game, boolean isWin) {
        UserEntity user = game.getUser();
        BigDecimal totalBet = game.getBet();
        BigDecimal balance = user.getBalance();

        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 BlackJackState getState(BlackJackGameEntity game) {
        int playerHandValue = calculateHandValue(game.getPlayerCards());

        if (playerHandValue == 21) {
            CardEntity hole = this.deckService.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 = 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;
    }

    private void dealCardsToDealerUntilMinimumScore(BlackJackGameEntity game) {
        while (calculateHandValue(game.getDealerCards()) < 17) {
            this.deckService.dealCardToDealer(game);
        }
    }
}


