package controllers.strategytest;

import components.FloatStatRow;
import components.NumberStatRow;
import interfaces.strategytest.BettingStrategyInterface;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import objects.Percentages;
import objects.SoccerMatch;
import objects.SoccerMatchAnalysis;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;


/**
 * The 1-3-2-6 system is similar to another positive progression betting system known as a Paroli.
 * Both involve increased stakes after a win, and both involve a cycle of bets ending at a fixed point.
 * The only real difference is the actual betting sequence.
 *
 * <p>
 * There are not many systems that are easier to understand than 1-3-2-6 and its simplicity is very appealing to many players.
 * There are also a number of other benefits that contribute to its popularity.
 *
 * <p>
 * Of course, as in any betting system, there are also disadvantages.
 * We explore the positive and negative aspects of the 1-3-2-6 below, along with the details and how it is used.
 *
 * <p>
 * Use of the 1-3-2-6 system
 * <p>
 * Like many betting systems, the 1-3-2-6 was designed for bets that pay out an even sum, like the red-black bets in roulette.
 * In fact, it is widely used on the roulette table and in other casino games such as baccarat, dice games and blackjack.
 * Basically, it doesn’t matter what you use it for, provided you are playing with even sums.
 *
 * <p>
 * As mentioned earlier, this is a very simple system.
 * All you have to do is adjust your bets based on a few simple rules.
 * First of all, you have to decide how high your betting unit should be.
 * As a guide, once you’ve set the amount you’re willing to lose in one session, your betting unit should be around 2% to 5% of that.
 *
 * <p>
 * For example, if you wanted to play with a total of 500 €, 10 € would be a reasonable number.
 * A bet unit is the amount you wager on the first bet in the sequence.
 * After every lost bet, the sequence ends and you start a new one – and again you insert a betting unit.
 *
 * <p>
 * If you win a bet, you adjust your stake according to the sequence 1-3-2-6.
 * So, if you win your first bet, the next time you increase your stake to € 30 (three betting units).
 * If you win this too, you will place € 20 on your next bet.
 * If you win this too, the next time you wager € 60.
 * And already we have the 1-3-2-6 sequence.
 *
 * <p>
 * If you win the fourth, consecutive bet then the sequence is completed and you start all over again.
 * Don’t forget, as soon as you lose, your stake will also reset to a betting unit.
 *
 * <p>
 * This is basically what the 1-3-2-6 betting system is all about.
 * As long as you keep the simple set of rules in mind, you shouldn’t have any problems using this system successfully.
 *
 * <p>
 * Possible outcomes of 1-3-2-6
 * <p>
 * Since this system is only a short cycle, there are few possible outcomes.
 * These are the following:
 * <p>
 * Loss of the first bet (total loss of one betting unit)
 * First bet won, second bet lost (total loss of two betting units)
 * First two bets won, third bet lost (total win of two betting units)
 * Won the first three bets, lose the fourth bet (breakeven)
 * Win all four bets (total win of 12 units)
 * So, as you can see, only two of the possible outcomes of a cycle involve loss.
 * A loss will only occur if the first or second bet is lost.
 * So, every time you win the first two bets, you cannot lose again in the same cycle.
 *
 * <p>
 * It may seem like the 1-3-2-6 system makes it more likely to make money than to lose.
 * But unfortunately, this is not true.
 * Unfortunately, while the system has some advantages, the guaranteed profit isn’t one.
 *
 * <p>
 * Advantages and disadvantages of the 1-3-2-6 betting strategy
 * <p>
 * Perhaps the greatest advantage of 1-3-2-6 is that it is very simple and easy to learn.
 * If you want to use a betting system that you don’t want to be overly complicated, this is a good option.
 * In addition, your stake is not increased after a loss – this prevents the rapid loss of money, especially if you choose the size of your betting units sensitively.
 *
 * <p>
 * Another notable advantage is that after stopping the sequence after four consecutive wins and then going back to one unit, you are facing the possibility of a small winning streak.
 * There is a chance that you will win all four times in a row – and if you are lucky, your winnings will exceed the losses on bets you lost before.
 *
 * <p>
 * The system does not come without its disadvantages; the most striking is that there is nothing to beat the house edge that exists in any casino.
 * No betting system can do this.
 * But it helps you to maintain discipline with a set of rules to be followed, because ultimately you still have to rely on your luck to win.
 * If you get the wrong sequence of results, you will lose.
 *
 * <p>
 * The 1-3-2-6 isn’t a bad betting system compared to any of the other systems available, provided you accept it for what it is.
 * This system doesn’t guarantee that you will win, but it can help you get the most out of your luck.
 */
public class OneThreeTwoSixStrategy extends VBox implements BettingStrategyInterface {

    List<SoccerMatch> matches = new ArrayList<>();
    private FloatStatRow betAmount;
    private NumberStatRow sequencesPlayedStatRow;
    private NumberStatRow sequencesWonStatRow;
    private NumberStatRow sequenceLostFirstStatRow;
    private NumberStatRow sequenceLostSecondStatRow;
    private NumberStatRow sequenceLostThirdStatRow;
    private FloatStatRow wonLossBankStatRow;
    private ScrollPane matchesSummaryScrollPane;

    public OneThreeTwoSixStrategy() {
        buildSettingsGuiPanel();
        buildStatGuiPanel();
        buildMatchSummaryPanel();
    }

    public OneThreeTwoSixStrategy(List<SoccerMatch> matches) {
        setMatches(matches);

        buildSettingsGuiPanel();
        buildStatGuiPanel();
        buildMatchSummaryPanel();
    }

    @Override
    public void setMatches(List<SoccerMatch> matches) {
        this.matches.clear();
        this.matches.addAll(matches);
    }

    @Override
    public void buildSettingsGuiPanel() {
        betAmount = new FloatStatRow("Bet Amount", 10f);
        this.getChildren().add(betAmount);
    }

    @Override
    public void buildStatGuiPanel() {
        sequencesPlayedStatRow = new NumberStatRow("Number of matches played", 0);
        sequencesWonStatRow = new NumberStatRow("Number of Sequences won", 0);
        sequenceLostFirstStatRow = new NumberStatRow("Number of Secuences lost first", 0);
        sequenceLostSecondStatRow = new NumberStatRow("Number of Secuences lost Second", 0);
        sequenceLostThirdStatRow = new NumberStatRow("Number of Secuences lost Third", 0);

        wonLossBankStatRow = new FloatStatRow("Amount won or lost", 0f);

        this.getChildren().add(sequencesPlayedStatRow);
        this.getChildren().add(sequencesWonStatRow);
        this.getChildren().add(sequenceLostFirstStatRow);
        this.getChildren().add(sequenceLostSecondStatRow);
        this.getChildren().add(sequenceLostThirdStatRow);
        this.getChildren().add(wonLossBankStatRow);
    }

    @Override
    public void buildMatchSummaryPanel() {
        matchesSummaryScrollPane = new ScrollPane();
        matchesSummaryScrollPane.setContent(new VBox());
        matchesSummaryScrollPane.setMaxHeight(200d);

        this.getChildren().add(matchesSummaryScrollPane);
    }

    // Saker att bestämma sig för.. Hur många sekvenser ska vara igång samtidigt?
    @Override
    public void runTest() {

        List<List<betSequenceDTO>> sequences = new ArrayList<>();
        int sequenceLimit = 5;

        for (int i = 0; i < matches.size(); i++) {
            SoccerMatch match = matches.get(i);
            sequencesPlayedStatRow.increaseValue();

            // Decide what to bet on.. If it is close to 2.0 it will be easier to calculate the bet sequence
            // If it is the first in a new sequence then use betAmount
            // If it is the second in a new sequence then use 3 * betAmount /* adjusted for odds and so on */
            // The bet amount should always conform to the strategy

            List<SoccerMatch> todaysMatches = filterTodaysMatches(matches, match);

            List<MatchPercentDTO> listOfPotentialMatchesOrdered = sortMatchesOnBetPotential(todaysMatches);

            List<List<betSequenceDTO>> sequencesToRemove = new ArrayList<>();
            for (MatchPercentDTO matchPercentDTO : listOfPotentialMatchesOrdered) {
                String betOn = shouldBetOn(matchPercentDTO.match);

                boolean addingToSequence = false;
                // find all sequences that are active and not containing a match with the same gameDate
                float currentBetAmount;
                float odds = getCurrentMatchOdds(matchPercentDTO.match, betOn);
                List<betSequenceDTO> currentSequence = new ArrayList<>();
                for (List<betSequenceDTO> sequence : sequences) {
                    if (sequence.stream().noneMatch(m -> m.match.getGameDate().toLocalDate().isEqual(match.getGameDate().toLocalDate()))) {
                        currentSequence = sequence;
                        addingToSequence = true;
                        break;
                    }
                }

                currentBetAmount = getBetAmout(currentSequence);
                wonLossBankStatRow.decreaseValue(currentBetAmount);
                currentSequence.add(new betSequenceDTO(matchPercentDTO.match, currentBetAmount, odds, betOn));

                if (!addingToSequence && sequences.size() < sequenceLimit) {
                    sequences.add(currentSequence);
                }

                if (betOn.contains(matchPercentDTO.match.getMatchResult())) {
                    wonLossBankStatRow.increaseValue(currentBetAmount * odds);
                    if (currentSequence.size() == 4) {
                        sequencesWonStatRow.increaseValue();
                        sequencesToRemove.add(currentSequence);
                    }
                } else {
                    switch (currentSequence.size()) {
                        case 1:
                            sequenceLostFirstStatRow.increaseValue();
                            sequencesToRemove.add(currentSequence);
                            break;
                        case 2:
                            sequenceLostSecondStatRow.increaseValue();
                            sequencesToRemove.add(currentSequence);
                            break;
                        case 3:
                            sequenceLostThirdStatRow.increaseValue();
                            sequencesToRemove.add(currentSequence);
                            break;
                        default:
                            break;
                    }
                }
            }

            for (List<betSequenceDTO> toRemove : sequencesToRemove) {
                addSequenceToStat(toRemove);
            }

            sequences.removeAll(sequencesToRemove);
            sequencesToRemove.clear();

            i += todaysMatches.size();
        }
    }

    private void addSequenceToStat(List<betSequenceDTO> toRemove) {
        VBox sequenceStat = new VBox();
        Label sequenceLengthLabel = new Label("Sequence length: " + toRemove.size());
        sequenceStat.getChildren().add(sequenceLengthLabel);
        for (betSequenceDTO seq : toRemove) {
            Label label = new Label(seq.match.getHomeTeam().getTeamName() + " " + seq.match.getAwayTeam().getTeamName() + " " + seq.match.getMatchResult());
            Label resultLabel = new Label(" Bet on: " + seq.bet.betOn + " " + seq.bet.betAmount + " " + seq.bet.odds);
            sequenceStat.getChildren().add(label);
            sequenceStat.getChildren().add(resultLabel);
        }
        Double sum = calculateSequenceSum(toRemove);
        Label sumLabel = new Label("Sum: " + sum);
        sequenceStat.getChildren().add(sumLabel);

        switch (toRemove.size()) {
            case 2:
                sequenceStat.setStyle("-fx-background-color: red");
                break;
            case 3:
                sequenceStat.setStyle("-fx-background-color: firebrick");
                break;
            case 4:
                if (toRemove.get(3).isWin()) {
                    sequenceStat.setStyle("-fx-background-color: green");
                } else {
                    sequenceStat.setStyle("-fx-background-color: aquamarine");
                }
                break;
            default:
                sequenceStat.setStyle("-fx-background-color: darkred");
                break;
        }

        ((VBox) matchesSummaryScrollPane.getContent()).getChildren().add(sequenceStat);
    }

    private Double calculateSequenceSum(List<betSequenceDTO> toRemove) {
        if (toRemove.size() == 1) {
            return (double) -toRemove.get(0).bet.betAmount;
        } else if (toRemove.size() == 2) {
            double sum = 0d;
            sum += toRemove.get(0).bet.betAmount * toRemove.get(0).bet.odds - toRemove.get(0).bet.betAmount;
            sum -= toRemove.get(1).bet.betAmount;
            return sum;
        } else if (toRemove.size() == 3) {
            double sum = 0d;
            sum += toRemove.get(0).bet.betAmount * toRemove.get(0).bet.odds - toRemove.get(0).bet.betAmount;
            sum += toRemove.get(1).bet.betAmount * toRemove.get(1).bet.odds - toRemove.get(1).bet.betAmount;
            sum -= toRemove.get(2).bet.betAmount;
            return sum;
        } else if (toRemove.size() == 4) {
            if (toRemove.get(3).isWin()) {
                double sum = 0d;
                sum += toRemove.get(0).bet.betAmount * toRemove.get(0).bet.odds - toRemove.get(0).bet.betAmount;
                sum += toRemove.get(1).bet.betAmount * toRemove.get(1).bet.odds - toRemove.get(1).bet.betAmount;
                sum += toRemove.get(2).bet.betAmount * toRemove.get(2).bet.odds - toRemove.get(2).bet.betAmount;
                sum += toRemove.get(3).bet.betAmount * toRemove.get(3).bet.odds - toRemove.get(3).bet.betAmount;
                return sum;
            } else {
                double sum = 0d;
                sum += toRemove.get(0).bet.betAmount * toRemove.get(0).bet.odds - toRemove.get(0).bet.betAmount;
                sum += toRemove.get(1).bet.betAmount * toRemove.get(1).bet.odds - toRemove.get(1).bet.betAmount;
                sum += toRemove.get(2).bet.betAmount * toRemove.get(2).bet.odds - toRemove.get(2).bet.betAmount;
                sum -= toRemove.get(3).bet.betAmount;
                return sum;
            }
        } else {
            return 0d;
        }
    }

    private float getCurrentMatchOdds(SoccerMatch match, String betOn) {
        return switch (betOn) {
            case "1" -> match.getOdds1();
            case "X" -> match.getOddsX();
            case "2" -> match.getOdds2();
            case "1X" -> calculate1XOdds(match);
            case "X2" -> calculateX2Odds(match);
            case "12" -> calculate12Odds(match);
            default -> 0;
        };
    }

    private float getBetAmout(List<betSequenceDTO> sequence) {
        int sequenceStep = sequence.size();
        if (sequenceStep == 1) {
            return 3 * betAmount.getValue();
        } else if (sequenceStep == 2) {
            return 2 * betAmount.getValue();
        } else if (sequenceStep == 3) {
            return 6 * betAmount.getValue();
        } else {
            return betAmount.getValue();
        }
    }

    @Override
    public String shouldBetOn(SoccerMatch match) {
        final String returnValue;
        float lowerLimit = 1.9f;
        float upperLimit = 2.1f;

        if (match.getOdds1() > lowerLimit && match.getOdds1() < upperLimit) {
            returnValue = "1";
        } else if (match.getOddsX() > lowerLimit && match.getOddsX() < upperLimit) {
            returnValue = "X";
        } else if (match.getOdds2() > lowerLimit && match.getOdds2() < upperLimit) {
            returnValue = "2";
        } else if (calculate1XOdds(match) > lowerLimit && calculate1XOdds(match) < upperLimit) {
            returnValue = "1X";
        } else if (calculateX2Odds(match) > lowerLimit && calculateX2Odds(match) < upperLimit) {
            returnValue = "X2";
        } else if (calculate12Odds(match) > lowerLimit && calculate12Odds(match) < upperLimit) {
            returnValue = "12";
        } else {
            returnValue = "";
        }

        return returnValue;
    }

    // Något odds måste vara när 2.0 för att spelas på
    @Override
    public List<MatchPercentDTO> sortMatchesOnBetPotential(List<SoccerMatch> matches) {
        List<SoccerMatch> filteredGames = new ArrayList<>();
        for (SoccerMatch sm : matches) {
            if (!shouldBetOn(sm).equals("")) {
                filteredGames.add(sm);
            }
        }

        return sortMatches(filteredGames);
    }

    private class betSequenceDTO {

        Bet bet;
        SoccerMatch match;

        betSequenceDTO(SoccerMatch match, float betAmount, float odds, String betOn) {
            this.match = match;
            bet = new Bet(betAmount, odds, betOn);
        }

        public boolean isWin() {
            if (match != null && bet.betOn.contains(match.getMatchResult())) {
                return true;
            }
            return false;
        }
    }

    private class Bet {
        private float betAmount;
        private float odds;
        String betOn;

        Bet(float betAmount, float odds, String betOn) {
            this.betAmount = betAmount;
            this.odds = odds;
            this.betOn = betOn;
        }
    }

    private List<MatchPercentDTO> sortMatches(List<SoccerMatch> matches) {

        List<MatchPercentDTO> dtoList = new ArrayList<>();
        for (SoccerMatch match : matches) {
            dtoList.add(new MatchPercentDTO(match));
        }

        return dtoList.stream().sorted().collect(Collectors.toList());
    }

    private class MatchPercentDTO implements Comparable<MatchPercentDTO> {
        SoccerMatch match;
        Percentages percentages;
        float calculatedOdds1;
        float calculatedOddsX;
        float calculatedOdds2;
        float diffOdds1;
        float diffOddsX;
        float diffOdds2;
        private final float diff1X;
        private final float diffX2;
        private final float diff12;

        public MatchPercentDTO(SoccerMatch match) {
            this.match = match;
            SoccerMatchAnalysis sma = new SoccerMatchAnalysis(match);
            percentages = sma.calculateWinPercentages();
            calculatedOdds1 = convertPercentageToOdds(percentages.getHomeWinPercentage());
            calculatedOddsX = convertPercentageToOdds(percentages.getDrawPercentage());
            calculatedOdds2 = convertPercentageToOdds(percentages.getAwayWinPercentage());

            diffOdds1 = calculatedOdds1 - match.getOdds1();
            diffOddsX = calculatedOddsX - match.getOddsX();
            diffOdds2 = calculatedOdds2 - match.getOdds2();

            diff1X = calculate1XOdds(match) - calculate1XOdds(calculatedOdds1, calculatedOddsX);
            diffX2 = calculate1XOdds(match) - calculateX2Odds(calculatedOddsX, calculatedOdds2);
            diff12 = calculate1XOdds(match) - calculate12Odds(calculatedOdds1, calculatedOdds2);

        }

        public Percentages getPercentages() {
            return percentages;
        }

        public float getCalculatedOdds1() {
            return calculatedOdds1;
        }

        public float getCalculatedOddsX() {
            return calculatedOddsX;
        }

        public float getCalculatedOdds2() {
            return calculatedOdds2;
        }

        public float getDiffOdds1() {
            return diffOdds1;
        }

        public float getDiffOddsX() {
            return diffOddsX;
        }

        public float getDiffOdds2() {
            return diffOdds2;
        }

        public float getDiff1X() {
            return diff1X;
        }

        public float getDiffX2() {
            return diffX2;
        }

        public float getDiff12() {
            return diff12;
        }

        @Override
        public int compareTo(MatchPercentDTO matchPercentDTO) {
            String betOn1 = shouldBetOn(match);
            String betOn2 = shouldBetOn(matchPercentDTO.match);

            if (betOn1.equals("1")) {
                if (betOn2.equals("1")) {
                    return Float.compare(diffOdds1, matchPercentDTO.getDiffOdds1());
                } else if (betOn2.equals("X")) {
                    return Float.compare(diffOdds1, matchPercentDTO.getDiffOddsX());
                } else if (betOn2.equals("2")) {
                    return Float.compare(diffOdds1, matchPercentDTO.getDiffOdds2());
                } else if (betOn2.equals("1X")) {
                    return Float.compare(diffOdds1, matchPercentDTO.getDiff1X());
                } else if (betOn2.equals("X2")) {
                    return Float.compare(diffOdds1, matchPercentDTO.getDiffX2());
                } else if (betOn2.equals("12")) {
                    return Float.compare(diffOdds1, matchPercentDTO.getDiff12());
                }
            } else if (betOn1.equals("X")) {
                if (betOn2.equals("1")) {
                    return Float.compare(diffOddsX, matchPercentDTO.getDiffOdds1());
                } else if (betOn2.equals("X")) {
                    return Float.compare(diffOddsX, matchPercentDTO.getDiffOddsX());
                } else if (betOn2.equals("2")) {
                    return Float.compare(diffOddsX, matchPercentDTO.getDiffOdds2());
                } else if (betOn2.equals("1X")) {
                    return Float.compare(diffOddsX, matchPercentDTO.getDiff1X());
                } else if (betOn2.equals("X2")) {
                    return Float.compare(diffOddsX, matchPercentDTO.getDiffX2());
                } else if (betOn2.equals("12")) {
                    return Float.compare(diffOddsX, matchPercentDTO.getDiff12());
                }
            } else if (betOn1.equals("2")) {
                if (betOn2.equals("1")) {
                    return Float.compare(diffOdds2, matchPercentDTO.getDiffOdds1());
                } else if (betOn2.equals("X")) {
                    return Float.compare(diffOdds2, matchPercentDTO.getDiffOddsX());
                } else if (betOn2.equals("2")) {
                    return Float.compare(diffOdds2, matchPercentDTO.getDiffOdds2());
                } else if (betOn2.equals("1X")) {
                    return Float.compare(diffOdds2, matchPercentDTO.getDiff1X());
                } else if (betOn2.equals("X2")) {
                    return Float.compare(diffOdds2, matchPercentDTO.getDiffX2());
                } else if (betOn2.equals("12")) {
                    return Float.compare(diffOdds2, matchPercentDTO.getDiff12());
                }
            } else if (betOn1.equals("1X")) {
                if (betOn2.equals("1")) {
                    return Float.compare(diff1X, matchPercentDTO.getDiffOdds1());
                } else if (betOn2.equals("X")) {
                    return Float.compare(diff1X, matchPercentDTO.getDiffOddsX());
                } else if (betOn2.equals("2")) {
                    return Float.compare(diff1X, matchPercentDTO.getDiffOdds2());
                } else if (betOn2.equals("1X")) {
                    return Float.compare(diff1X, matchPercentDTO.getDiff1X());
                } else if (betOn2.equals("X2")) {
                    return Float.compare(diff1X, matchPercentDTO.getDiffX2());
                } else if (betOn2.equals("12")) {
                    return Float.compare(diff1X, matchPercentDTO.getDiff12());
                }
            } else if (betOn1.equals("X2")) {
                if (betOn2.equals("1")) {
                    return Float.compare(diffX2, matchPercentDTO.getDiffOdds1());
                } else if (betOn2.equals("X")) {
                    return Float.compare(diffX2, matchPercentDTO.getDiffOddsX());
                } else if (betOn2.equals("2")) {
                    return Float.compare(diffX2, matchPercentDTO.getDiffOdds2());
                } else if (betOn2.equals("1X")) {
                    return Float.compare(diffX2, matchPercentDTO.getDiff1X());
                } else if (betOn2.equals("X2")) {
                    return Float.compare(diffX2, matchPercentDTO.getDiffX2());
                } else if (betOn2.equals("12")) {
                    return Float.compare(diffX2, matchPercentDTO.getDiff12());
                }
            } else if (betOn1.equals("12")) {
                if (betOn2.equals("1")) {
                    return Float.compare(diff12, matchPercentDTO.getDiffOdds1());
                } else if (betOn2.equals("X")) {
                    return Float.compare(diff12, matchPercentDTO.getDiffOddsX());
                } else if (betOn2.equals("2")) {
                    return Float.compare(diff12, matchPercentDTO.getDiffOdds2());
                } else if (betOn2.equals("1X")) {
                    return Float.compare(diff12, matchPercentDTO.getDiff1X());
                } else if (betOn2.equals("X2")) {
                    return Float.compare(diff12, matchPercentDTO.getDiffX2());
                } else if (betOn2.equals("12")) {
                    return Float.compare(diff12, matchPercentDTO.getDiff12());
                }
            }

            return 0;
        }

        private float getDiffToBetOn(String whatToBetOn) {
            if (whatToBetOn.equals("1")) {
                return diffOdds1;
            } else if (whatToBetOn.equals("X")) {
                return diffOddsX;
            } else if (whatToBetOn.equals("2")) {
                return diffOdds2;
            } else if (whatToBetOn.equals("1X")) {
                return diff1X;
            } else if (whatToBetOn.equals("X2")) {
                return diffX2;
            } else if (whatToBetOn.equals("12")) {
                return diff12;
            }
            return 0;
        }
    }
}
