package tests;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;

import data.GuiMysql;
import data.TestDatabase;
import objects.League;
import objects.SoccerMatch;
import objects.Team;
import objects.bets.AsianHandicap;
import tests.objects.LeagueDTO;
import tests.objects.Standing;

public abstract class TestClass {

    String date;
    float startingBank;
    float bettingLevel;
    float betMargin;
    int lookBack;
    int sportId;
    int countryId;
    int leagueId;
    protected int bestAwayLookback;
    protected int bestAwayMargin;
    protected int bestDrawMargin;
    protected int bestDrawLookback;
    protected int bestHomeMargin;
    protected int bestHomeLookback;
    protected float bestDrawBank;
    protected float bestAwayBank;
    protected float bestHomeBank;

    ArrayList<results> possitiveResults = new ArrayList<>();

    public abstract void runTest();

    public ArrayList<SoccerMatch> getMatches(PreparedStatement stmt) {
        final ArrayList<SoccerMatch> matches = Lists.newArrayList();

        try {

            final ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                final SoccerMatch sm = new SoccerMatch();
                final Team homeTeam = new Team();
                final Team awayTeam = new Team();

                homeTeam.setTeamId(rs.getInt("homeTeam"));
                awayTeam.setTeamId(rs.getInt("awayTeam"));
                homeTeam.setTeamName(rs.getString("homeTeamName"));
                awayTeam.setTeamName(rs.getString("awayTeamName"));
                homeTeam.setTeamLeagueId(rs.getInt("leagueId"));
                awayTeam.setTeamLeagueId(rs.getInt("leagueId"));
                homeTeam.setCountryId(rs.getInt("countryId"));
                awayTeam.setCountryId(rs.getInt("countryId"));

                sm.setAwayScore(rs.getInt("awayScore"));
                sm.setHomeScore(rs.getInt("homeScore"));
                sm.setHomeTeam(homeTeam);
                sm.setAwayTeam(awayTeam);
                sm.setMatchId(rs.getInt("id"));
                sm.setOdds1(rs.getFloat("odds1"));
                sm.setOddsX(rs.getFloat("oddsX"));
                sm.setOdds2(rs.getFloat("odds2"));
                sm.setGameDate(LocalDateTime.parse(rs.getString("gameDate")));
                sm.setSeason(rs.getString("season"));

                matches.add(sm);
            }
        } catch (final SQLException e) {
            e.printStackTrace();
        }

        return matches;
    }

    public void setup(String date, float startingBank, float bettingLevel, Float betMargin, int lookBack, int sportId,
            Integer countryId,
            Integer leagueId) {
        this.startingBank = startingBank;
        this.bettingLevel = bettingLevel;
        this.betMargin = betMargin;
        this.lookBack = lookBack;
        this.sportId = sportId;
        this.countryId = countryId;
        this.leagueId = leagueId;
        this.date = date;
    }

    public Connection getConnection() {
        return GuiMysql.getInstance().getDbConnection();
    }

    public League getLeagueInfoById() {
        return GuiMysql.getInstance().getLeagueInfo(leagueId);
    }

    public League getLeagueInfoByName(String leagueName) {
        return GuiMysql.getInstance().getLeagueInfo(leagueName);
    }

    public void CalculateLookback(int leagueId, LocalDateTime date, int daysToCheck) {
        final LocalDateTime startDate = date.minusDays(daysToCheck);
        final String sql = "SELECT res.*, " + "hTeam.name as homeTeamName, aTeam.name as awayTeamName "
                + "FROM SoccerResults as res "
                + "Join Team as hTeam ON res.homeTeam = hTeam.id " + "Join Team as aTeam ON res.awayTeam = aTeam.id "
                + "WHERE res.leagueId = ? AND DATE(gameDate) < ? AND DATE(gameDate) > ? ORDER BY gameDate ASC";

        try {
            final PreparedStatement stmt = getConnection().prepareStatement(sql);
            stmt.setInt(1, leagueId);
            stmt.setString(2, date.format(DateTimeFormatter.ISO_DATE));
            stmt.setString(3, startDate.format(DateTimeFormatter.ISO_DATE));

            final ArrayList<SoccerMatch> matches = getMatches(stmt);
            stmt.close();

            if (matches.size() > 0) {
                this.bestHomeBank = startingBank;
                this.bestDrawBank = startingBank;
                this.bestAwayBank = startingBank;

                // TODO Fixa s� att man kontrollerar 1 år bakåt i tiden som Filtered Matches,
                // plocka bort om possitive results är färreän 50%
                recalcHome(matches);
                recalcDraw(matches);
                recalcAway(matches);
            }
        } catch (final SQLException e) {
            e.printStackTrace();
        }

    }

    public void placeAsianLinesBet(SoccerMatch soccerMatch, AsianHandicap bet) {

    }

    private void recalcHome(ArrayList<SoccerMatch> matches) {
        if (this.bestHomeBank < 0) {
            this.bestHomeBank = startingBank;
        }

        this.bestHomeLookback = 0;
        this.bestHomeMargin = 0;

        final float betLevel = bettingLevel / 100f;

        final LocalDateTime currentDate = matches.get(0).getGameDate();
        LocalDate localDate = currentDate.toLocalDate();
        float betAmount = startingBank * betLevel;

        possitiveResults.clear();

        for (int lookBack = 4; lookBack < 25; lookBack++) {
            for (int betMargin = 4; betMargin < 35; betMargin++) {
                final float betMarginDecimal = 1 + (betMargin / (float) 100);
                float bank = startingBank;
                for (final SoccerMatch match : matches) {
                    if (match.getHomeScore() < 0 || match.getAwayScore() < 0) {
                        continue;
                    }

                    final List<SoccerMatch> homeMatches = matches.stream().filter(
                            p -> p.getGameDate().isBefore(match.getGameDate())
                                    && p.getHomeTeam().getTeamId() == match.getHomeTeam().getTeamId())
                            .limit(lookBack).collect(Collectors.toList());
                    final List<SoccerMatch> awayMatches = matches.stream().filter(
                            p -> p.getGameDate().isBefore(match.getGameDate())
                                    && p.getAwayTeam().getTeamId() == match.getAwayTeam().getTeamId())
                            .limit(lookBack).collect(Collectors.toList());

                    final long hemmaVinster = homeMatches.stream().filter(p -> p.getHomeScore() > p.getAwayScore())
                            .count();
                    final long bortaForluster = awayMatches.stream().filter(p -> p.getAwayScore() < p.getHomeScore())
                            .count();

                    final float homeWinPercent = (hemmaVinster + bortaForluster)
                            / Float.valueOf(homeMatches.size() + awayMatches.size()) * 100;
                    final float homeOdds = 100 / homeWinPercent;

                    if (localDate.isBefore(match.getGameDate().toLocalDate())) {
                        betAmount = bank * betLevel;
                        localDate = match.getGameDate().toLocalDate();
                    }

                    if (homeOdds * betMarginDecimal <= match.getOdds1()) {
                        bank = bank - betAmount;
                        if (match.getHomeScore() > match.getAwayScore()) {
                            bank = bank + betAmount * match.getOdds1();
                        }
                    }

                }
                if (bank > startingBank) {
                    final int lookBack2 = lookBack;
                    final int betMargin2 = betMargin;
                    final java.util.Optional<results> found = possitiveResults.stream()
                            .filter(r -> r.getLookback() == lookBack2 && r.getMargin() == betMargin2).findFirst();
                    if (found.isPresent()) {
                        final results results = found.get();
                        results.addCount();
                        results.addBank(bank);
                    } else {
                        possitiveResults.add(new results(lookBack, betMargin, bank));
                    }
                }
            }
        }
        Collections.sort(possitiveResults);
        if (possitiveResults.size() > 0) {
            final results res = possitiveResults.get(0);
            this.bestHomeLookback = res.getLookback();
            this.bestHomeMargin = res.getMargin();
            this.bestHomeBank = res.getBank();
        }
    }

    private void recalcDraw(ArrayList<SoccerMatch> matches) {
        if (this.bestDrawBank < 0) {
            this.bestDrawBank = startingBank;
        }

        this.bestDrawLookback = 0;
        this.bestDrawMargin = 0;

        final float betLevel = bettingLevel / 100f;

        final LocalDateTime currentDate = matches.get(0).getGameDate();
        LocalDate localDate = currentDate.toLocalDate();
        float betAmount = startingBank * betLevel;

        possitiveResults.clear();

        for (int lookBack = 4; lookBack < 25; lookBack++) {
            for (int betMargin = 4; betMargin < 35; betMargin++) {
                final float betMarginDecimal = 1 + (betMargin / (float) 100);
                float bank = startingBank;
                for (final SoccerMatch match : matches) {
                    if (match.getHomeScore() < 0 || match.getAwayScore() < 0) {
                        continue;
                    }

                    final List<SoccerMatch> homeMatches = matches.stream().filter(
                            p -> p.getGameDate().isBefore(match.getGameDate())
                                    && p.getHomeTeam().getTeamId() == match.getHomeTeam().getTeamId())
                            .limit(lookBack).collect(Collectors.toList());
                    final List<SoccerMatch> awayMatches = matches.stream().filter(
                            p -> p.getGameDate().isBefore(match.getGameDate())
                                    && p.getAwayTeam().getTeamId() == match.getAwayTeam().getTeamId())
                            .limit(lookBack).collect(Collectors.toList());

                    final long hemmaLika = homeMatches.stream().filter(p -> p.getHomeScore() == p.getAwayScore())
                            .count();
                    final long bortaLika = awayMatches.stream().filter(p -> p.getAwayScore() == p.getHomeScore())
                            .count();

                    final float drawPercent = (hemmaLika + bortaLika)
                            / Float.valueOf(homeMatches.size() + awayMatches.size()) * 100;
                    final float drawOdds = 100 / drawPercent;

                    if (localDate.isBefore(match.getGameDate().toLocalDate())) {
                        betAmount = bank * betLevel;
                        localDate = match.getGameDate().toLocalDate();
                    }

                    if (drawOdds * betMarginDecimal <= match.getOddsX()) {
                        bank = bank - betAmount;
                        if (match.getHomeScore() == match.getAwayScore()) {
                            bank = bank + betAmount * match.getOddsX();
                        }
                    }

                }
                if (bank > startingBank) {
                    final int lookBack2 = lookBack;
                    final int betMargin2 = betMargin;
                    final java.util.Optional<results> found = possitiveResults.stream()
                            .filter(r -> r.getLookback() == lookBack2 && r.getMargin() == betMargin2).findFirst();
                    if (found.isPresent()) {
                        final results results = found.get();
                        results.addCount();
                        results.addBank(bank);
                    } else {
                        possitiveResults.add(new results(lookBack, betMargin, bank));
                    }
                }
            }
        }
        Collections.sort(possitiveResults);
        if (possitiveResults.size() > 0) {
            final results res = possitiveResults.get(0);
            this.bestDrawLookback = res.getLookback();
            this.bestDrawMargin = res.getMargin();
            this.bestDrawBank = res.getBank();
        }
    }

    private void recalcAway(ArrayList<SoccerMatch> matches) {
        if (this.bestAwayBank < 0) {
            this.bestAwayBank = startingBank;
        }

        this.bestAwayLookback = 0;
        this.bestAwayMargin = 0;

        final float betLevel = bettingLevel / 100f;

        final LocalDateTime currentDate = matches.get(0).getGameDate();
        LocalDate localDate = currentDate.toLocalDate();
        float betAmount = startingBank * betLevel;

        possitiveResults.clear();

        for (int lookBack = 4; lookBack < 25; lookBack++) {
            for (int betMargin = 4; betMargin < 35; betMargin++) {
                final float betMarginDecimal = 1 + (betMargin / (float) 100);
                float bank = startingBank;
                for (final SoccerMatch match : matches) {
                    if (match.getHomeScore() < 0 || match.getAwayScore() < 0) {
                        continue;
                    }

                    final List<SoccerMatch> homeMatches = matches.stream().filter(
                            p -> p.getGameDate().isBefore(match.getGameDate())
                                    && p.getHomeTeam().getTeamId() == match.getHomeTeam().getTeamId())
                            .limit(lookBack).collect(Collectors.toList());
                    final List<SoccerMatch> awayMatches = matches.stream().filter(
                            p -> p.getGameDate().isBefore(match.getGameDate())
                                    && p.getAwayTeam().getTeamId() == match.getAwayTeam().getTeamId())
                            .limit(lookBack).collect(Collectors.toList());

                    final long hemmaForluster = homeMatches.stream().filter(p -> p.getHomeScore() < p.getAwayScore())
                            .count();
                    final long bortaVinster = awayMatches.stream().filter(p -> p.getAwayScore() > p.getHomeScore())
                            .count();

                    final float awayWinPercent = (hemmaForluster + bortaVinster)
                            / Float.valueOf(homeMatches.size() + awayMatches.size()) * 100;
                    final float awayOdds = 100 / awayWinPercent;

                    if (localDate.isBefore(match.getGameDate().toLocalDate())) {
                        betAmount = bank * betLevel;
                        localDate = match.getGameDate().toLocalDate();
                    }

                    if (awayOdds * betMarginDecimal <= match.getOdds2()) {
                        bank = bank - betAmount;
                        if (match.getHomeScore() < match.getAwayScore()) {
                            bank = bank + betAmount * match.getOdds2();
                        }
                    }

                }

                if (bank > startingBank) {
                    final int lookBack2 = lookBack;
                    final int betMargin2 = betMargin;
                    final java.util.Optional<results> found = possitiveResults.stream()
                            .filter(r -> r.getLookback() == lookBack2 && r.getMargin() == betMargin2).findFirst();
                    if (found.isPresent()) {
                        final results results = found.get();
                        results.addCount();
                        results.addBank(bank);
                    } else {
                        possitiveResults.add(new results(lookBack, betMargin, bank));
                    }
                }
            }
        }

        Collections.sort(possitiveResults);
        if (possitiveResults.size() > 0) {
            final results res = possitiveResults.get(0);
            this.bestAwayLookback = res.getLookback();
            this.bestAwayMargin = res.getMargin();
            this.bestAwayBank = res.getBank();
        }
    }

    protected List<Standing> getStandings(List<SoccerMatch> seasonMatches) {
        List<Standing> result = new ArrayList<>();
        for (SoccerMatch match : seasonMatches) {
            Optional<Standing> homeTeam = result.stream()
                    .filter(p -> p.getTeamName().equals(match.getHomeTeam().getTeamName())).findFirst();
            Optional<Standing> awayTeam = result.stream()
                    .filter(p -> p.getTeamName().equals(match.getAwayTeam().getTeamName())).findFirst();

            updateStanding(result, match, homeTeam, awayTeam);
        }
        return result;
    }

    protected void updateStanding(List<Standing> result, SoccerMatch match, Optional<Standing> homeTeam,
            Optional<Standing> awayTeam) {
        int homeScore = match.getHomeScore();
        int awayScore = match.getAwayScore();
        if (homeScore > awayScore) {
            homeTeam.ifPresentOrElse(ht -> {
                ht.setWins(ht.getWins() + 1);
                ht.setGamesPlayed(ht.getGamesPlayed() + 1);
                ht.setPoints(ht.getPoints() + 3);
            }, () -> result.add(new Standing(match.getHomeTeam().getTeamName(), 1, 0, 0, 3, 1)));

            awayTeam.ifPresentOrElse(at -> {
                at.setLosses(at.getLosses() + 1);
                at.setGamesPlayed(at.getGamesPlayed() + 1);
            }, () -> result.add(new Standing(match.getAwayTeam().getTeamName(), 0, 0, 1, 0, 1)));

        } else if (homeScore == awayScore) {

            homeTeam.ifPresentOrElse(ht -> {
                ht.setDraws(ht.getDraws() + 1);
                ht.setGamesPlayed(ht.getGamesPlayed() + 1);
                ht.setPoints(ht.getPoints() + 1);
            }, () -> result.add(new Standing(match.getHomeTeam().getTeamName(), 0, 1, 0, 1, 1)));
            awayTeam.ifPresentOrElse(at -> {
                at.setDraws(at.getDraws() + 1);
                at.setGamesPlayed(at.getGamesPlayed() + 1);
                at.setPoints(at.getPoints() + 1);
            }, () -> result.add(new Standing(match.getHomeTeam().getTeamName(), 0, 1, 0, 1, 1)));
        } else if (homeScore < awayScore) {
            homeTeam.ifPresentOrElse(ht -> {
                ht.setLosses(ht.getLosses() + 1);
                ht.setGamesPlayed(ht.getGamesPlayed() + 1);
            }, () -> result.add(new Standing(match.getHomeTeam().getTeamName(), 0, 0, 1, 0, 1)));
            awayTeam.ifPresentOrElse(at -> {
                at.setWins(at.getWins() + 1);
                at.setGamesPlayed(at.getGamesPlayed() + 1);
                at.setPoints(at.getPoints() + 3);
            }, () -> result.add(new Standing(match.getHomeTeam().getTeamName(), 1, 0, 0, 3, 1)));
        }
    }

    protected LinkedHashMap<String, List<SoccerMatch>> getMatchesOrderedBySeason(LeagueDTO leagueDTO) {
        List<SoccerMatch> matches = TestDatabase.getInstance().getMatches(leagueDTO);

        LinkedHashMap<String, List<SoccerMatch>> matchesBySeason = new LinkedHashMap<>();
        matches.forEach(sm -> {
            List<SoccerMatch> matchesInSeason = matchesBySeason.getOrDefault(sm.getSeason(), new ArrayList<>());
            matchesInSeason.add(sm);
            matchesBySeason.put(sm.getSeason(), matchesInSeason);
        });

        LinkedHashMap<String, List<SoccerMatch>> sortedMap = new LinkedHashMap<>();
        matchesBySeason.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
                .forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));
        return sortedMap;
    }

    public class results implements Comparable<results> {
        int lookback;
        int margin;
        int count;
        float bank;

        public results(int lookback, int margin, float bank) {
            this.lookback = lookback;
            this.margin = margin;
            this.bank = bank;
            count = 1;
        }

        public float getBank() {
            return bank;
        }

        public void addBank(float bank) {
            this.bank = (this.bank + bank) / 2;
        }

        public void addCount() {
            count++;
        }

        public int getCount() {
            return count;
        }

        public int getLookback() {
            return lookback;
        }

        public int getMargin() {
            return margin;
        }

        public void setLookback(int lookback) {
            this.lookback = lookback;
        }

        public void setMargin(int margin) {
            this.margin = margin;
        }

        @Override
        public int compareTo(results res) {
            int returnValue = 0;
            if (this.count > res.getCount()) {
                returnValue = -1;
            } else if (this.count == res.getCount()) {
                if (this.bank > res.getBank()) {
                    returnValue = -1;
                } else if (this.bank < res.getBank()) {
                    returnValue = 1;
                } else {
                    returnValue = 0;
                }
            } else if (this.count < res.getCount()) {
                returnValue = 1;
            }
            return returnValue;
        }
    }
}
