package controllers;

import data.GuiMysql;
import data.MatchStatSql;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import objects.OddsStat;
import objects.SoccerMatch;
import objects.Team;

import java.net.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MatchStatisticsController implements Initializable {

    private static MatchStatisticsController instance;
    @FXML
    public DatePicker datePicker;
    @FXML
    public ListView<SoccerMatch> MatchesListView;

    private SoccerMatch selectedMatch = null;
    public Pane MatchPanel;
    Map<Integer, List<SoccerMatch>> allMatches = new HashMap<>();
    int currentLeagueId = -1;

    private MatchStatSql database = new MatchStatSql();

    public static MatchStatisticsController getInstance() {
        if (instance == null) {
            synchronized (MatchStatisticsController.class) {
                if (instance == null) {
                    instance = new MatchStatisticsController();
                }
            }
        }
        return instance;
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        instance = this;

        MatchesListView.getSelectionModel().selectedItemProperty().addListener((ov, oldValue, newValue) -> {
            if (newValue != null && !newValue.equals(oldValue)) {
                ClearSelectedSoccerMatch();
                UpdateSelectedSoccerMatch(newValue);
            }
        });

        datePicker.valueProperty().addListener((ov, oldValue, newValue) -> {
            if (oldValue != null && !newValue.isEqual(oldValue)) {
                populateGamesList(newValue);
            } else if (oldValue == null && newValue != null) {
                populateGamesList(newValue);
            }
        });
    }

    private void populateGamesList(LocalDate date) {
        List<SoccerMatch> matches = GuiMysql.getInstance().getPrioMatchesByDate(date.toString());

        MatchesListView.getItems().clear();
        for (SoccerMatch match : matches) {
            MatchesListView.getItems().add(match);
        }
    }

    private void UpdateSelectedSoccerMatch(SoccerMatch soccerMatch) {
        selectedMatch = soccerMatch;

        buildSoccerMatchAnalysisPanel(selectedMatch);
    }

    private void buildSoccerMatchAnalysisPanel(SoccerMatch match) {
        VBox baseBox = new VBox();
        int limitNumerOfGames = 10;
        //baseBox.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
        HBox topRow = new HBox(10d);
        HBox bottomRow = new HBox(10d);
        baseBox.getChildren().add(topRow);
        baseBox.getChildren().add(bottomRow);

        MatchPanel.getChildren().add(baseBox);

        VBox scoringTotalBoxHome = new VBox();
        topRow.getChildren().add(scoringTotalBoxHome);
        scoringTotalBoxHome.getChildren().add(new Label("Result Home - Away goals"));
        VBox scoringTotalBoxAway = new VBox();
        topRow.getChildren().add(scoringTotalBoxAway);
        scoringTotalBoxAway.getChildren().add(new Label("Result Away - Home goals"));
        VBox resultBoxTotalScore = new VBox();
        Label resultingLabel = new Label("Result Compare Value");
        resultBoxTotalScore.getChildren().add(resultingLabel);
        topRow.getChildren().add(resultBoxTotalScore);

        for (int i = 1; i <= limitNumerOfGames; i++) {
            HBox box = new HBox(5d);
            box.getChildren().add(new Label(String.valueOf(i)));
            int totalGoalResultHome = database.totalGoalResult(match.getHomeTeam().getTeamId(), match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), true, i);
            box.getChildren().add(new Label(String.valueOf(totalGoalResultHome)));
            scoringTotalBoxHome.getChildren().add(box);

            HBox boxAway = new HBox(5d);
            boxAway.getChildren().add(new Label(String.valueOf(i)));
            int totalGoalResultAway = database.totalGoalResult(match.getAwayTeam().getTeamId(), match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), false, i);
            boxAway.getChildren().add(new Label(String.valueOf(totalGoalResultAway)));
            scoringTotalBoxAway.getChildren().add(boxAway);

            OddsStat totalGoalStat = statTotalGoalResult((totalGoalResultHome - totalGoalResultAway), match.getHomeTeam().getTeamLeagueId(), i, match.getGameDate());
            resultBoxTotalScore.getChildren().add(new Label(String.valueOf(totalGoalResultHome - totalGoalResultAway) +
                    totalGoalStat.toString() + " (" + totalGoalStat.getAvgGoalsScores() + ")"));
        }

        VBox goalsScoredHomeAndAwayHome = new VBox();
        topRow.getChildren().add(goalsScoredHomeAndAwayHome);
        goalsScoredHomeAndAwayHome.getChildren().add(new Label("Goals Scored Total Home"));
        VBox goalsScoredHomeAndAwayAway = new VBox();
        topRow.getChildren().add(goalsScoredHomeAndAwayAway);
        goalsScoredHomeAndAwayAway.getChildren().add(new Label("Goals Scored Total Away"));

        VBox resultHomeAway = new VBox();
        resultHomeAway.getChildren().add(new Label("HomeVsAway Compare value"));

        topRow.getChildren().add(resultHomeAway);
        for (int i = 1; i <= limitNumerOfGames; i++) {
            HBox box = new HBox(5d);
            box.getChildren().add(new Label(String.valueOf(i)));
            float totalGoalResultHomeANDAwayHome = database.totalGoalResultHomeANDAway(match.getHomeTeam().getTeamId(), match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), i);
            box.getChildren().add(new Label(String.valueOf(totalGoalResultHomeANDAwayHome)));
            goalsScoredHomeAndAwayHome.getChildren().add(box);

            HBox boxAway = new HBox(5d);
            boxAway.getChildren().add(new Label(String.valueOf(i)));
            float totalGoalResultHomeANDAwayAway = database.totalGoalResultHomeANDAway(match.getAwayTeam().getTeamId(), match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), i);
            boxAway.getChildren().add(new Label(String.valueOf(totalGoalResultHomeANDAwayAway)));
            goalsScoredHomeAndAwayAway.getChildren().add(boxAway);

            resultHomeAway.getChildren().add(new Label(String.format("%.2f",
                    totalGoalResultHomeANDAwayHome - totalGoalResultHomeANDAwayAway) + " " +
                    statSplitGoalResultHomeAndAway(Math.round(totalGoalResultHomeANDAwayHome - totalGoalResultHomeANDAwayAway),
                            match.getHomeTeam().getTeamLeagueId(), i, match.getGameDate()).toString()));
        }

        VBox goalsScoredLastXGamesHome = new VBox();
        bottomRow.getChildren().add(goalsScoredLastXGamesHome);
        goalsScoredLastXGamesHome.getChildren().add(new Label("Goals Scored Home"));
        VBox goalsScoredLastXGamesAway = new VBox();
        bottomRow.getChildren().add(goalsScoredLastXGamesAway);
        goalsScoredLastXGamesAway.getChildren().add(new Label("Goals Scored Away"));

        VBox goalsScoredLastXGamesCV = new VBox();
        goalsScoredLastXGamesCV.getChildren().add(new Label("Goals Scored Compare value"));

        bottomRow.getChildren().add(goalsScoredLastXGamesCV);
        for (int i = 1; i <= limitNumerOfGames; i++) {
            HBox box = new HBox(5d);
            box.getChildren().add(new Label(String.valueOf(i)));
            float goalsScoredLastXGamesHomeValue = database.goalsScoredLastXGames(match.getHomeTeam().getTeamId(), true, match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), i);
            box.getChildren().add(new Label(String.valueOf(goalsScoredLastXGamesHomeValue)));
            goalsScoredLastXGamesHome.getChildren().add(box);

            HBox boxAway = new HBox(5d);
            boxAway.getChildren().add(new Label(String.valueOf(i)));
            float goalsScoredLastXGamesAwayValue = database.goalsScoredLastXGames(match.getAwayTeam().getTeamId(), false, match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), i);
            boxAway.getChildren().add(new Label(String.valueOf(goalsScoredLastXGamesAwayValue)));
            goalsScoredLastXGamesAway.getChildren().add(boxAway);

            OddsStat oddsStat = statGoalsScoredLastXGames((goalsScoredLastXGamesHomeValue - goalsScoredLastXGamesAwayValue), match.getHomeTeam().getTeamLeagueId(), i, match.getGameDate());
            goalsScoredLastXGamesCV.getChildren().add(new Label(String.format("%.2f",
                    goalsScoredLastXGamesHomeValue - goalsScoredLastXGamesAwayValue) + oddsStat.toString()
                    + " (" + oddsStat.getAvgGoalsScores() + ")"
            ));
        }


        VBox goalsScoreLastXGamesHomeANDAwayHome = new VBox();
        bottomRow.getChildren().add(goalsScoreLastXGamesHomeANDAwayHome);
        goalsScoreLastXGamesHomeANDAwayHome.getChildren().add(new Label("Goals Scored Home/Away Home"));
        VBox goalsScoreLastXGamesHomeANDAwayAway = new VBox();
        bottomRow.getChildren().add(goalsScoreLastXGamesHomeANDAwayAway);
        goalsScoreLastXGamesHomeANDAwayAway.getChildren().add(new Label("Goals Scored Home/Away Away"));

        VBox goalsScoreLastXGamesHomeANDAwayCV = new VBox();
        goalsScoreLastXGamesHomeANDAwayCV.getChildren().add(new Label("Goals Scored Home/Away Compare value"));

        bottomRow.getChildren().add(goalsScoreLastXGamesHomeANDAwayCV);
        for (int i = 1; i <= limitNumerOfGames; i++) {
            HBox box = new HBox(5d);
            box.getChildren().add(new Label(String.valueOf(i)));
            float goalsScoreLastXGamesHomeANDAwayHomeValue = database.goalsScoreLastXGamesHomeANDAway(match.getHomeTeam().getTeamId(), match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), i);
            box.getChildren().add(new Label(String.valueOf(goalsScoreLastXGamesHomeANDAwayHomeValue)));
            goalsScoreLastXGamesHomeANDAwayHome.getChildren().add(box);

            HBox boxAway = new HBox(5d);
            boxAway.getChildren().add(new Label(String.valueOf(i)));
            float goalsScoreLastXGamesHomeANDAwayAwayValue = database.goalsScoreLastXGamesHomeANDAway(match.getAwayTeam().getTeamId(), match.getGameDate().format(DateTimeFormatter.ISO_LOCAL_DATE), i);
            boxAway.getChildren().add(new Label(String.valueOf(goalsScoreLastXGamesHomeANDAwayAwayValue)));
            goalsScoreLastXGamesHomeANDAwayAway.getChildren().add(boxAway);

            OddsStat statGoalsScoredLastXGamesHomeAndAway = statGoalsScoredLastXGamesHomeAndAway((goalsScoreLastXGamesHomeANDAwayHomeValue - goalsScoreLastXGamesHomeANDAwayAwayValue), match.getHomeTeam().getTeamLeagueId(), i, match.getGameDate());
            goalsScoreLastXGamesHomeANDAwayCV.getChildren().add(new Label(String.format("%.2f",
                    goalsScoreLastXGamesHomeANDAwayHomeValue - goalsScoreLastXGamesHomeANDAwayAwayValue) + " " + statGoalsScoredLastXGamesHomeAndAway.toString() + "(" + statGoalsScoredLastXGamesHomeAndAway.getAvgGoalsScores() + ")"));
        }
    }


    private List<SoccerMatch> getAllMatchesFromLeauge(int leagueId) {
        if (allMatches.get(leagueId) == null || allMatches.get(leagueId).isEmpty()) {
            allMatches.put(leagueId, database.getAllMatchesForLeague(leagueId));
        }
        return allMatches.get(leagueId);
    }

    private int getTotalGoalResultsHomeAndAway(int teamId, int leagueId, int limit, String currentDate) {
        List<SoccerMatch> matches = getAllMatchesFromLeauge(leagueId);

        List<SoccerMatch> matchesBefore = matches.stream().filter(m -> m.getGameDate().isBefore(LocalDateTime.parse(currentDate))).collect(Collectors.toList());

        List<SoccerMatch> teamMatches = matchesBefore.stream().filter(m -> m.getHomeTeam().getTeamId() == teamId || m.getAwayTeam().getTeamId() == teamId).limit(limit).collect(Collectors.toList());

        return calculateTotalGoalResultHomeAndAway(teamId, teamMatches);
    }

    private static int calculateTotalGoalResultHomeAndAway(int teamId, List<SoccerMatch> teamMatches) {
        int result = 0;
        for (SoccerMatch tm : teamMatches) {
            if (tm.getHomeTeam().getTeamId() == teamId) {
                result += tm.getHomeScore() - tm.getAwayScore();
            } else if (tm.getAwayTeam().getTeamId() == teamId) {
                result += tm.getAwayScore() - tm.getHomeScore();
            }
        }
        return result;
    }

    private OddsStat statSplitGoalResultHomeAndAway(int value, int league, int limit, LocalDateTime currentDate) {
        OddsStat stat = new OddsStat();

        for (SoccerMatch sm : getAllMatchesFromLeauge(league)) {
            List<SoccerMatch> homeTeamMatches = filterSoccerMatchesByTeam(league, currentDate, sm.getHomeTeam(), limit);
            List<SoccerMatch> awayTeamMatches = filterSoccerMatchesByTeam(league, currentDate, sm.getAwayTeam(), limit);

            int homeTeamValue = calculateTotalGoalResultHomeAndAway(sm.getHomeTeam().getTeamId(), homeTeamMatches);
            int awayTeamValue = calculateTotalGoalResultHomeAndAway(sm.getAwayTeam().getTeamId(), awayTeamMatches);

            if (homeTeamValue - awayTeamValue == value) {
                if (sm.getHomeScore() > sm.getAwayScore()) {
                    stat.addHomeWin();
                } else if (sm.getHomeScore() == sm.getAwayScore()) {
                    stat.addDraw();
                } else if (sm.getAwayScore() > sm.getHomeScore()) {
                    stat.addAwayWin();
                }
            }
        }
        return stat;
    }

    private OddsStat statGoalsScoredLastXGames(float value, int leagueId, int limit, LocalDateTime currentDate) {
        OddsStat stat = new OddsStat();
        for (SoccerMatch sm : getAllMatchesFromLeauge(leagueId)) {
            List<SoccerMatch> homeTeamMatches = filterSoccerMatchesByTeamHomeOrAway(leagueId, currentDate, sm.getHomeTeam(), limit, true);
            List<SoccerMatch> awayTeamMatches = filterSoccerMatchesByTeamHomeOrAway(leagueId, currentDate, sm.getAwayTeam(), limit, false);

            int goalsScoredHome = homeTeamMatches.stream().mapToInt(SoccerMatch::getHomeScore).sum();
            int goalsScoredAway = awayTeamMatches.stream().mapToInt(SoccerMatch::getAwayScore).sum();

            if (((float) goalsScoredHome - (float) goalsScoredAway) == value) {
                if (sm.getHomeScore() > sm.getAwayScore()) {
                    stat.addHomeWin();
                } else if (sm.getHomeScore() == sm.getAwayScore()) {
                    stat.addDraw();
                } else if (sm.getAwayScore() > sm.getHomeScore()) {
                    stat.addAwayWin();
                }
                stat.addGoalsScored(sm.getHomeScore() + sm.getAwayScore());
            }
        }
        return stat;
    }

    private OddsStat statGoalsScoredLastXGamesHomeAndAway(float value, int leagueId, int limit, LocalDateTime currentDate) {
        OddsStat stat = new OddsStat();
        for (SoccerMatch sm : getAllMatchesFromLeauge(leagueId)) {
            List<SoccerMatch> homeTeamMatches = filterSoccerMatchesByTeam(leagueId, currentDate, sm.getHomeTeam(), limit);
            List<SoccerMatch> awayTeamMatches = filterSoccerMatchesByTeam(leagueId, currentDate, sm.getAwayTeam(), limit);

            int homeTeamRes = calculateScoreLastXGamesHomeAndAway(homeTeamMatches, sm.getHomeTeam().getTeamId());
            int awayTeamRes = calculateScoreLastXGamesHomeAndAway(awayTeamMatches, sm.getAwayTeam().getTeamId());
            if (((float) homeTeamRes - (float) awayTeamRes) == value) {
                if (sm.getHomeScore() > sm.getAwayScore()) {
                    stat.addHomeWin();
                } else if (sm.getHomeScore() == sm.getAwayScore()) {
                    stat.addDraw();
                } else if (sm.getAwayScore() > sm.getHomeScore()) {
                    stat.addAwayWin();
                }
                stat.addGoalsScored(sm.getHomeScore() + sm.getAwayScore());
            }
        }
        return stat;
    }

    private int calculateScoreLastXGamesHomeAndAway(List<SoccerMatch> teamMatches, int teamId) {
        int res = 0;
        for (SoccerMatch sm : teamMatches) {
            if (sm.getHomeTeam().getTeamId() == teamId) {
                res += sm.getHomeScore();
            } else if (sm.getAwayTeam().getTeamId() == teamId) {
                res += sm.getAwayScore();
            }
        }

        return res;
    }


    private OddsStat statTotalGoalResult(int value, int leagueId, int limit, LocalDateTime currentDate) {
        OddsStat stat = new OddsStat();
        for (SoccerMatch sm : getAllMatchesFromLeauge(leagueId)) {
            List<SoccerMatch> homeTeamMatches = filterSoccerMatchesByTeamHomeOrAway(leagueId, currentDate, sm.getHomeTeam(), limit, true);
            List<SoccerMatch> awayTeamMatches = filterSoccerMatchesByTeamHomeOrAway(leagueId, currentDate, sm.getAwayTeam(), limit, false);

            int homeValue = homeTeamMatches.stream().mapToInt(match -> match.getHomeScore() - match.getAwayScore()).sum();
            int awayValue = awayTeamMatches.stream().mapToInt(match -> match.getAwayScore() - match.getHomeScore()).sum();

            if ((homeValue - awayValue) == value) {
                if (sm.getHomeScore() > sm.getAwayScore()) {
                    stat.addHomeWin();
                } else if (sm.getHomeScore() == sm.getAwayScore()) {
                    stat.addDraw();
                } else if (sm.getAwayScore() > sm.getHomeScore()) {
                    stat.addAwayWin();
                }
                stat.addGoalsScored(sm.getHomeScore() + sm.getAwayScore());
            }
        }
        return stat;
    }

    private List<SoccerMatch> filterSoccerMatchesByTeamHomeOrAway(int league, LocalDateTime currentDate, Team sm, int limit, boolean homeTeam) {
        Stream<SoccerMatch> stream = getAllMatchesFromLeauge(league).stream().filter(f -> f.getGameDate().isBefore(currentDate));
        if (homeTeam) {
            stream = stream.filter(f -> f.getHomeTeam().getTeamId() == sm.getTeamId());
        } else {
            stream = stream.filter(f -> f.getAwayTeam().getTeamId() == sm.getTeamId());
        }
        return stream.limit(limit).toList();
    }

    private List<SoccerMatch> filterSoccerMatchesByTeam(int league, LocalDateTime currentDate, Team sm, int limit) {
        return getAllMatchesFromLeauge(league).stream()
                .filter(f -> f.getGameDate().isBefore(currentDate) &&
                        (f.getHomeTeam().getTeamId() == sm.getTeamId() ||
                                f.getAwayTeam().getTeamId() == sm.getTeamId()))
                .limit(limit).toList();
    }

    private void ClearSelectedSoccerMatch() {
        MatchPanel.getChildren().clear();
    }
}
