package tests;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;

import com.google.common.collect.Lists;

import data.TestDatabase;
import objects.OverUnder;
import objects.SoccerMatch;
import tests.objects.LeagueDTO;
import tests.objects.Standing;

public class FibonacciDrawTest extends TestClass {

    TestDatabase database;
    List<SoccerMatch> matches;
    Map<String, List<SoccerMatch>> matchesBySeason = new HashMap<>();

    private int atleastXGamesPlayed = 10;
    private float drawPercentageLimit = 0.25f;

    List<Integer> fibonacciSequence = Lists.newArrayList(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377);
    List<BetDTO> bets = new ArrayList<>();
    float bank;

    int currentBetSeries;

    private float betAmount = 10f;

    List<SoccerMatch> betsToPlace = new ArrayList<>();

    @Override
    public void runTest() {
        currentBetSeries = 0;
        bank = startingBank;
        database = TestDatabase.getInstance();
        LeagueDTO leagueDTO = database.getLeagueDTO(leagueId);

        LinkedHashMap<String, List<SoccerMatch>> matches = getMatchesOrderedBySeason(leagueDTO);

        boolean skipFirst = true;
        for (Entry<String, List<SoccerMatch>> entry : matches.entrySet()) {
            if (skipFirst) {
                skipFirst = false;
                continue;
            }
            analyseSeason(entry.getKey(), entry.getValue());
            break; // TODO remove, testing one season at a time
        }
    }

    private void analyseSeason(String season, List<SoccerMatch> seasonMatches) {

        SoccerMatch tempSm = seasonMatches.get(0);
        LocalDate currentBetDate = tempSm.getGameDate().toLocalDate();
//        float betAmount = bank * (bettingLevel / 100f);
        List<OverUnder> statsOverUnderWithDrawStats = database.getStatsOverUnderWithDrawStats(
                tempSm.getHomeTeam().getTeamLeagueId(), tempSm.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE));

        for (SoccerMatch soccerMatch : seasonMatches) {
            List<Standing> standings = getStandings(
                    seasonMatches.stream().filter(
                            p -> p.getGameDate().toLocalDate().isBefore(soccerMatch.getGameDate().toLocalDate()))
                            .toList());
            Optional<Standing> homeTeamStandingOptional = standings.stream()
                    .filter(s -> s.getTeamName().equals(soccerMatch.getHomeTeamName())).findFirst();
            Optional<Standing> awayTeamStandingOptional = standings.stream()
                    .filter(s -> s.getTeamName().equals(soccerMatch.getAwayTeamName())).findFirst();

            if (homeTeamStandingOptional.isPresent() && awayTeamStandingOptional.isPresent()) {
                Standing homeTeamStanding = homeTeamStandingOptional.get();
                Standing awayTeamStanding = awayTeamStandingOptional.get();

                if (homeTeamStanding.getGamesPlayed() > atleastXGamesPlayed
                        && awayTeamStanding.getGamesPlayed() > atleastXGamesPlayed) {

                    if (!soccerMatch.getGameDate().toLocalDate().isEqual(currentBetDate)) {
                        placeBets(betAmount, statsOverUnderWithDrawStats);
                        // check bets and update bank with winnings
                        // update bet amount
                        statsOverUnderWithDrawStats = database.getStatsOverUnderWithDrawStats(
                                soccerMatch.getHomeTeam().getTeamLeagueId(),
                                soccerMatch.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE));

                        checkCurrentUnresolvedBets(currentBetDate);
                        currentBetDate = soccerMatch.getGameDate().toLocalDate();
                        // betAmount = bank * (bettingLevel / 100f);
                    }

                    float roundedDiff = database.getRoundedDiff(soccerMatch);
                    Optional<OverUnder> drawStat = statsOverUnderWithDrawStats.stream()
                            .filter(p -> p.getKey().equals(roundedDiff)).findFirst();
                    if (drawStat.isPresent()) {
                        OverUnder overUnder = drawStat.get();
                        float drawPercentage = overUnder.getDraws() / (float) overUnder.getTotalGames();
                        if (drawPercentage > drawPercentageLimit) {
                            // Make Bet!
                            soccerMatch.setRoundedDiff(roundedDiff);
                            soccerMatch.setDrawPercetage(drawPercentage);
                            betsToPlace.add(soccerMatch);
                        }
                    }
                }
            }
        }

        System.out.println("Season done, final bank " + bank + " outstanding bets:");
        bets.forEach(b -> System.out.println(b.toString()));
    }

    private void placeBets(float betAmount, List<OverUnder> statsOverUnderWithDrawStats) {

        betsToPlace.sort((b1, b2) -> Float.compare(b2.getDrawPercent(), b1.getDrawPercent()));

        for (SoccerMatch sm : betsToPlace) {

            Optional<OverUnder> drawStat = statsOverUnderWithDrawStats.stream()
                    .filter(p -> p.getKey().equals(sm.getRoundedDiff())).findFirst();
            if (drawStat.isPresent()) {
                OverUnder overUnder = drawStat.get();
                float drawPercentage = overUnder.getDraws() / (float) overUnder.getTotalGames();
                if (drawPercentage > drawPercentageLimit) {
                    // Make Bet!
                    placeBet(sm, betAmount);
                }
            }
        }
        betsToPlace.clear();
    }

    private void checkCurrentUnresolvedBets(LocalDate currentBetDate) {
        List<BetDTO> activeBets = bets.stream()
                .filter(p -> p.soccerMatch.getGameDate().toLocalDate().equals(currentBetDate))
                .toList();

        for (BetDTO betDTO : activeBets) {
            if (betDTO.soccerMatch.getHomeScore() == betDTO.soccerMatch.getAwayScore()) {

                bank += betDTO.betAmount * betDTO.soccerMatch.getOddsX();
                System.out.println(
                        "Total winnings " + betDTO.betAmount * betDTO.soccerMatch.getOddsX() + " New bank: " + bank
                                + " odds " + betDTO.oddsX);

                System.out.println("REMOVING BETS");
                bets.stream()
                        .filter(p -> p.betSeriesId == betDTO.betSeriesId)
                        .forEach(b -> System.out.println(b));
                bets.removeIf(p -> p.betSeriesId == betDTO.betSeriesId);
            }
        }
    }

    private void placeBet(SoccerMatch soccerMatch, float betAmount) {
        List<BetDTO> todaysBets = bets.stream()
                .filter(p -> p.soccerMatch.getGameDate().toLocalDate().equals(soccerMatch.getGameDate().toLocalDate()))
                .toList();

        Optional<BetDTO> openBetSeries = bets.stream()
                .filter(p -> !todaysBets.stream().map(b -> b.betSeriesId).toList().contains(p.betSeriesId)).findFirst();
        float currentBetAmount;
        int currentBetLosses;
        int betSeries;
        if (openBetSeries.isPresent()) {
            BetDTO betDTO = openBetSeries.get();
            currentBetLosses = (int) bets.stream().filter(p -> p.betSeriesId == betDTO.betSeriesId).count();
            betSeries = betDTO.betSeriesId;
        } else {
            currentBetLosses = 0;
            Optional<BetDTO> max = bets.stream().max((b1, b2) -> Integer.compare(b1.betSeriesId, b2.betSeriesId));
            if (max.isPresent()) {
                betSeries = max.get().betSeriesId + 1;
            } else {
                betSeries = 0;
            }
        }
        currentBetAmount = betAmount * fibonacciSequence.get(currentBetLosses);
        bets.add(new BetDTO(betSeries, soccerMatch, currentBetAmount, soccerMatch.getOddsX()));

        bank -= currentBetAmount;

        System.out.println(
                soccerMatch.getGameDate().toLocalDate() + " Betting on " + soccerMatch.getHomeTeamName() + " - "
                        + soccerMatch.getAwayTeamName() + " amount " + currentBetAmount + " the "
                        + currentBetLosses + " in seqence with draw percent: " + soccerMatch.getDrawPercent()
                        + " result "
                        + soccerMatch.getHomeScore() + " - " + soccerMatch.getAwayScore());
    }

    private class BetDTO {

        SoccerMatch soccerMatch;
        float betAmount;
        float oddsX;
        int betSeriesId;

        public BetDTO(int betSeriesId, SoccerMatch soccerMatch, float betAmount, float oddsX) {
            this.soccerMatch = soccerMatch;
            this.betAmount = betAmount;
            this.oddsX = oddsX;
            this.betSeriesId = betSeriesId;
        }

        @Override
        public String toString() {
            return soccerMatch.getHomeTeamName() + " - " + soccerMatch.getAwayTeamName() + " bet amount " + betAmount
                    + " betSeries: " + betSeriesId;
        }
    }

}
