package controllers.strategytest;

import components.FloatStatRow;
import components.IntTableStatRow;
import components.NumberStatRow;
import interfaces.strategytest.BettingStrategyInterface;
import javafx.scene.layout.VBox;
import objects.SoccerMatch;
import objects.SoccerMatchAnalysis;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * The basic concept of betting according to the Fibonacci sequence is simple: bet on a tie.
 If you lose, just bet on the next tie according to a certain key and start increasing your stake.

 *
 * It is important that the odds for the tie are above 2.62 (more precisely: 2.618);
 * luckily for those who want to employ this strategy, there are many tie bets for this minimum odds.
 The higher the rate, the better.

 *
 * Fibonacci Betting Strategy
 * Betting after Fibonacci
 * If you lose, you simply bet the next tie after a Fibonacci sequence.
 It is a sequence of numbers in which the sum of two consecutive numbers results in the next number, making the Fibonacci sequence an infinite sequence of natural numbers.
 You can find more information about the Fibonacci sequence on Wikipedia.

 *
 * Fibonacci numbers
 * The Fibonacci sequence
 * So if you start betting with 1 € on the first draw with odds> 2.
62 and lose, then the next time you bet 1 € again, then 2 €, then 3 €, then 5 €, then 8 €, then 13 € and so on.

 *
 * It is mathematically understandable that every profit that you will achieve with this bet will offset the previous losses and you will even make a net profit.

 *
 * An example of the Fibonacci bet
 *
 * Let’s take the example of the situation where you lose ten times in a row and win your bet the eleventh time.
 In this case, you have already gambled away 143 € and finally successfully placed the eleventh number in the Fibonacci sequence, 144, on a bet.
 If we now assume that the successful bet had odds of @ 2.
80, you win 403.
20 €.
 You have wagered a total of 287 €, making your net profit 116.
20 €.

 *
 * The disadvantage of this strategy is also obvious.
 Assuming you lose not ten bets in a row, but twenty, then you would have already lost 16,910 € in stakes.
 This is a huge bankroll, which you should of course never risk for such a betting sequence.
 You can find more about this in our explanations on bankroll management.

 */
public class FibonacciStrategy extends VBox implements BettingStrategyInterface {

    public static final String NAME = "Fibonacci";
    List<SoccerMatch> matches;
    private FloatStatRow betAmount;
    private FloatStatRow wonLossBankStatRow;
    private NumberStatRow sequencesPlayedStatRow;
    private IntTableStatRow resultStatRow;

    private final int maxActiveFibonacciSequences = 4;
    private final List<FibonacciSequence> fibonacciSequences = new ArrayList<>();
    private NumberStatRow sequencesWonStatRow;

    public FibonacciStrategy() {
        super();
        buildSettingsGuiPanel();
        buildStatGuiPanel();
        buildMatchSummaryPanel();
    }

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

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

    @Override
    public void buildStatGuiPanel() {
        sequencesPlayedStatRow = new NumberStatRow("Number of sequences played", 0);
        sequencesWonStatRow = new NumberStatRow("Number of sequences won", 0);
        resultStatRow = new IntTableStatRow("Longest sequence without win");

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

        this.getChildren().add(sequencesPlayedStatRow);
        this.getChildren().add(sequencesWonStatRow);
        this.getChildren().add(resultStatRow);
        this.getChildren().add(wonLossBankStatRow);
    }

    @Override
    public void buildMatchSummaryPanel() {
        // Empty for now
    }

    @Override
    public void runTest() {

        float lowestBankAmount = 0f;

        for (int i = 0; i < matches.size(); i++) {
            SoccerMatch m = matches.get(i);
            List<SoccerMatch> soccerMatches = filterTodaysMatches(matches, m);

            List<SoccerMatch> sortedMatchList = sortMatchesOnBetPotential(soccerMatches);

            for (SoccerMatch match : sortedMatchList) {
                boolean removeSequence = false;
                String shouldBetOn = shouldBetOn(match);
                FibonacciSequence activeSequence = null;
                if (shouldBetOn.equals("X")) {
                    Optional<FibonacciSequence> fibSeq = fibonacciSequences.stream().filter(fs -> !fs.containsMatchFromSameDate(match)).findFirst();
                    if (fibSeq.isPresent()) {
                        // Add to current sequence
                        activeSequence = fibSeq.get();
                        if (activeSequence.bets.size() + 1 > FibonacciSequence.fibonacciSequence.size()) {
                            System.out.println("System FAILED " + match.getGameDate() + " lost " + activeSequence.totalBetValue + " current bank " + wonLossBankStatRow.getValue());
                            System.out.println("Lowest bank amount " + lowestBankAmount);
                            removeSequence = true;
                        } else {
                            activeSequence.addBet(match);
                            wonLossBankStatRow.decreaseValue(activeSequence.currentBetValue);
                            if (wonLossBankStatRow.getValue() < lowestBankAmount) {
                                lowestBankAmount = wonLossBankStatRow.getValue();
                            }
                        }
                    } else {
                        // Start new if not at max active sequences
                        if (fibonacciSequences.size() < maxActiveFibonacciSequences) {
                            activeSequence = new FibonacciSequence(match);
                            sequencesPlayedStatRow.increaseValue();
                            wonLossBankStatRow.decreaseValue(activeSequence.currentBetValue);
                            fibonacciSequences.add(activeSequence);
                            if (wonLossBankStatRow.getValue() < lowestBankAmount) {
                                lowestBankAmount = wonLossBankStatRow.getValue();
                            }
                        } else {
                            // Should not add more sequences, break loop
                            break;
                        }
                    }
                }

                if (activeSequence != null) {
                    if (removeSequence) {
                        // Sequence reached max size
                        fibonacciSequences.remove(activeSequence);
                    } else if (match.getMatchResult().equals("X")) {
                        // WON
                        resultStatRow.increaseValue(activeSequence.bets.size());
                        sequencesWonStatRow.increaseValue();

                        final float wonAmount = activeSequence.currentBetValue * match.getOddsX();
                        wonLossBankStatRow.increaseValue(wonAmount);
                        // Sequence won, remove sequence
                        fibonacciSequences.remove(activeSequence);
                    }
                }
            }
            i += sortedMatchList.size();
        }
        System.out.println("FibonacciStrategy.runTest() lest unresolved sequences: ");
        fibonacciSequences.forEach(fs -> System.out.println("Sequence: " + fs.bets.size() + " outstanding bets value: " + fs.totalBetValue));
    }

    /**
     * Matchen måste ha ett odds på X på minst 2.618
     */
    @Override
    public String shouldBetOn(SoccerMatch match) {
        return match.getOddsX() >= 2.618 ? "X" : "";
    }

    @Override
    public List<SoccerMatch> sortMatchesOnBetPotential(List<SoccerMatch> matches) {
        matches.sort((m2, m1) -> Float.compare(new SoccerMatchAnalysis(m1).calculateWinPercentages().getDrawPercentage(),
                new SoccerMatchAnalysis(m2).calculateWinPercentages().getDrawPercentage()));
        return matches;
    }

    public void reset() {
        fibonacciSequences.clear();
        sequencesPlayedStatRow.getChildren().clear();
        sequencesWonStatRow.getChildren().clear();
        resultStatRow.getChildren().clear();
        wonLossBankStatRow.getChildren().clear();

        buildStatGuiPanel();
        buildMatchSummaryPanel();
    }


    private class FibonacciSequence {

        public static final List<Integer> fibonacciSequence = Arrays.asList(1, 1, 2, 3, 5, 8, 13, 21, 34, 55); //, 89, 144, 233, 377, 610);
        List<SoccerMatch> bets = new ArrayList<>();
        float totalBetValue = 0f;

        float currentBetValue = 0f;

        FibonacciSequence(SoccerMatch match) {
            bets.add(match);
            totalBetValue += betAmount.getValue();
            currentBetValue = betAmount.getValue();
        }

        public void addBet(SoccerMatch match) {
            totalBetValue += betAmount.getValue() * fibonacciSequence.get(bets.size());
            currentBetValue = betAmount.getValue() * fibonacciSequence.get(bets.size());

            bets.add(match);
        }

        public boolean containsBet(SoccerMatch match) {
            return bets.contains(match);
        }

        public boolean containsMatchFromSameDate(SoccerMatch match) {
            return bets.stream().anyMatch(b -> b.getGameDate().toLocalDate().isEqual(match.getGameDate().toLocalDate()));
        }
    }
}
