<?php

class FantasyPoints {
    private $db;
    private $settingModel;

    // Define the points scoring rules
    private const POINTS_RULES = [
        'minutes_played' => [
            'up_to_60' => 1,
            'from_60' => 2,
        ],
        'goal' => [
            'Goalkeeper' => 6,
            'Defender' => 6,
            'Midfielder' => 5,
            'Forward' => 4,
        ],
        'assist' => 3,
        'clean_sheet' => [
            'Goalkeeper' => 4,
            'Defender' => 4,
            'Midfielder' => 1,
            'Forward' => 0,
        ],
        'goals_conceded' => [ // Per 2 goals for GK/DEF
            'Goalkeeper' => -1,
            'Defender' => -1,
        ],
        'penalty_saved' => 5,
        'penalty_missed' => -2,
        'yellow_card' => -1,
        'red_card' => -3,
    ];


    public function __construct() {
        $this->db = new Database;
        $this->settingModel = new Setting();
    }

    /**
     * Main function to calculate and save points for an entire gameweek.
     */
    public function calculateForGameweek($gameweek, $league_id) {
        // Get all finished fixtures for the gameweek
        $this->db->query("SELECT id FROM fixtures WHERE gameweek = :gameweek AND league_id = :league_id AND status = 'finished'");
        $this->db->bind(':gameweek', $gameweek);
        $this->db->bind(':league_id', $league_id);
        $fixtures = $this->db->resultSet();

        if (empty($fixtures)) {
            return ['status' => 'info', 'message' => 'No finished fixtures found for Gameweek ' . $gameweek];
        }

        $this->db->beginTransaction();
        try {
            // Clear existing points for this gameweek to prevent duplicates
            $fixture_ids_for_gameweek = array_map(fn($f) => $f->id, $fixtures);
            $placeholders = implode(',', array_fill(0, count($fixture_ids_for_gameweek), '?'));
            $this->db->query("DELETE FROM fantasy_player_points WHERE fixture_id IN ($placeholders)");
            $this->db->execute($fixture_ids_for_gameweek);

            // Calculate points for each fixture
            foreach ($fixtures as $fixture) {
                $this->_calculateAndSavePointsForFixture($fixture->id, $gameweek);
            }

            // After calculating for all fixtures, update the main fantasy team scores
            $this->_updateFantasyTeamTotals($gameweek);

            $this->db->commit();
            return ['status' => 'success', 'message' => 'Points calculated successfully for Gameweek ' . $gameweek];
        } catch (Exception $e) {
            $this->db->rollBack();
            return ['status' => 'error', 'message' => 'An error occurred: ' . $e->getMessage()];
        }
    }

    /**
     * Recalculates points for a specific fixture.
     * Useful if match stats are updated after initial calculation.
     * @param int $fixture_id
     * @return array Status message.
     */
    public function recalculateForFixture($fixture_id) {
        // 1. Get fixture details to find the gameweek
        $this->db->query("SELECT gameweek, status FROM fixtures WHERE id = :id");
        $this->db->bind(':id', $fixture_id);
        $fixture = $this->db->single();

        if (!$fixture) {
            return ['status' => 'error', 'message' => 'Fixture not found.'];
        }

        if ($fixture->status !== 'finished') {
            return ['status' => 'error', 'message' => 'Fixture is not finished. Points can only be calculated for finished matches.'];
        }

        $gameweek = $fixture->gameweek;

        $this->db->beginTransaction();
        try {
            // 2. Delete existing points for this fixture
            $this->db->query("DELETE FROM fantasy_player_points WHERE fixture_id = :fixture_id");
            $this->db->bind(':fixture_id', $fixture_id);
            $this->db->execute();

            // 3. Recalculate points for the fixture
            $this->_calculateAndSavePointsForFixture($fixture_id, $gameweek);

            // 4. Update team totals for the gameweek
            $this->_updateFantasyTeamTotals($gameweek);

            $this->db->commit();
            return ['status' => 'success', 'message' => 'Points recalculated successfully for Fixture #' . $fixture_id];
        } catch (Exception $e) {
            $this->db->rollBack();
            return ['status' => 'error', 'message' => 'An error occurred: ' . $e->getMessage()];
        }
    }

    /**
     * Calculates and saves points for a single fixture.
     */
    private function _calculateAndSavePointsForFixture($fixture_id, $gameweek) {
        // Fetch fixture details, lineups, and events
        $fixture = (new Fixture())->findById($fixture_id);
        $home_lineup = (new Lineup())->getLineupForFixture($fixture_id, $fixture->home_team_id);
        $away_lineup = (new Lineup())->getLineupForFixture($fixture_id, $fixture->away_team_id);
        $events = (new MatchEvent())->getEventsForFixture($fixture_id);

        $player_points = [];

        // 1. Initialize points for all players in the lineup
        $home_starter_ids = array_keys($home_lineup->starters);
        $away_starter_ids = array_keys($away_lineup->starters);
        $all_player_ids = array_unique(array_merge($home_starter_ids, $home_lineup->substitutes, $away_starter_ids, $away_lineup->substitutes));

        foreach ($all_player_ids as $player_id) {
            $player_points[$player_id] = ['points' => 0, 'breakdown' => []];
        }

        // Create a map of player to team for easy lookup
        $player_team_map = [];
        foreach ($home_starter_ids as $pid) { $player_team_map[$pid] = $fixture->home_team_id; }
        foreach ($home_lineup->substitutes as $pid) { $player_team_map[$pid] = $fixture->home_team_id; }
        foreach ($away_starter_ids as $pid) { $player_team_map[$pid] = $fixture->away_team_id; }
        foreach ($away_lineup->substitutes as $pid) { $player_team_map[$pid] = $fixture->away_team_id; }

        // Fetch positions for all players involved to avoid N+1 queries
        $player_positions = [];
        if (!empty($all_player_ids)) {
            $placeholders = implode(',', array_fill(0, count($all_player_ids), '?'));
            $this->db->query("SELECT user_id, position FROM players WHERE user_id IN ($placeholders)");
            $rows = $this->db->resultSet($all_player_ids);
            foreach ($rows as $r) {
                $player_positions[$r->user_id] = $r->position;
            }
        }

        // 2. Calculate minutes played and clean sheets
        $minutes_played = $this->_calculateMinutesPlayed($events, $home_lineup, $away_lineup);
        $clean_sheet_home = $fixture->away_team_score == 0;
        $clean_sheet_away = $fixture->home_team_score == 0;

        foreach ($minutes_played as $player_id => $minutes) {
            if (!isset($player_points[$player_id])) continue;
            $position = $player_positions[$player_id] ?? 'Midfielder'; // Default fallback

            // Points for playing
            if ($minutes >= 60) {
                $pts = self::POINTS_RULES['minutes_played']['from_60'];
                $player_points[$player_id]['points'] += $pts;
                $player_points[$player_id]['breakdown']['minutes'] = $pts;
            } elseif ($minutes > 0) {
                $pts = self::POINTS_RULES['minutes_played']['up_to_60'];
                $player_points[$player_id]['points'] += $pts;
                $player_points[$player_id]['breakdown']['minutes'] = $pts;
            }

            // Points for clean sheets (if played 60+ mins)
            if ($minutes >= 60) {
                $player_team_id = $player_team_map[$player_id] ?? null;
                $is_clean_sheet = false;
                if ($player_team_id === $fixture->home_team_id && $clean_sheet_home) {
                    $is_clean_sheet = true;
                } elseif ($player_team_id === $fixture->away_team_id && $clean_sheet_away) {
                    $is_clean_sheet = true;
                }
                
                if ($is_clean_sheet) {
                    $pts = self::POINTS_RULES['clean_sheet'][$position] ?? 0;
                    if ($pts > 0) {
                        $player_points[$player_id]['points'] += $pts;
                        $player_points[$player_id]['breakdown']['clean_sheet'] = $pts;
                    }
                }
            }

            // Deduct points for goals conceded (GK & DEF only)
            if (in_array($position, ['Goalkeeper', 'Defender'])) {
                $player_team_id = $player_team_map[$player_id] ?? null;
                $conceded = 0;
                if ($player_team_id === $fixture->home_team_id) {
                    $conceded = $fixture->away_team_score;
                } elseif ($player_team_id === $fixture->away_team_id) {
                    $conceded = $fixture->home_team_score;
                }
                if ($conceded > 0) {
                    $deduction = floor($conceded / 2) * (self::POINTS_RULES['goals_conceded'][$position] ?? 0);
                    if ($deduction != 0) {
                        $player_points[$player_id]['points'] += $deduction;
                        $player_points[$player_id]['breakdown']['goals_conceded'] = $deduction;
                    }
                }
            }
        }

        // 3. Process match events (goals, assists, cards)
        foreach ($events as $event) {
            // Handle assists first, as they are part of a 'goal' event
            if ($event->event_type === 'goal' && !empty($event->assisted_by_player_id)) {
                $assist_player_id = $event->assisted_by_player_id;
                if (isset($player_points[$assist_player_id])) {
                    $pts = self::POINTS_RULES['assist'];
                    $player_points[$assist_player_id]['points'] += $pts;
                    $player_points[$assist_player_id]['breakdown']['assists'] = ($player_points[$assist_player_id]['breakdown']['assists'] ?? 0) + $pts;
                }
            }

            // Handle events for the main player
            $player_id = $event->player_id;
            if (!$player_id || !isset($player_points[$player_id])) continue;

            $position = $player_positions[$player_id] ?? 'Midfielder';

            switch ($event->event_type) {
                case 'goal':
                case 'penalty_scored':
                    $pts = self::POINTS_RULES['goal'][$position] ?? 0;
                    $player_points[$player_id]['points'] += $pts;
                    $player_points[$player_id]['breakdown']['goals'] = ($player_points[$player_id]['breakdown']['goals'] ?? 0) + $pts;
                    break;
                case 'yellow_card':
                    $pts = self::POINTS_RULES['yellow_card'];
                    $player_points[$player_id]['points'] += $pts;
                    $player_points[$player_id]['breakdown']['cards'] = ($player_points[$player_id]['breakdown']['cards'] ?? 0) + $pts;
                    break;
                case 'red_card':
                    $pts = self::POINTS_RULES['red_card'];
                    $player_points[$player_id]['points'] += $pts;
                    $player_points[$player_id]['breakdown']['cards'] = ($player_points[$player_id]['breakdown']['cards'] ?? 0) + $pts;
                    break;
                case 'penalty_missed':
                    $pts = self::POINTS_RULES['penalty_missed'];
                    $player_points[$player_id]['points'] += $pts;
                    $player_points[$player_id]['breakdown']['pen_miss'] = ($player_points[$player_id]['breakdown']['pen_miss'] ?? 0) + $pts;
                    break;
                case 'penalty_saved':
                    if ($position === 'Goalkeeper') {
                        $pts = self::POINTS_RULES['penalty_saved'];
                        $player_points[$player_id]['points'] += $pts;
                        $player_points[$player_id]['breakdown']['pen_save'] = ($player_points[$player_id]['breakdown']['pen_save'] ?? 0) + $pts;
                    }
                    break;
            }
        }

        // 3.5 Bonus Points for Man of the Match
        if (!empty($fixture->man_of_the_match_player_id)) {
            $motm_id = $fixture->man_of_the_match_player_id;
            // Ensure the player is in the points array (they should be if they played)
            if (isset($player_points[$motm_id])) {
                $bonus = 3; // Standard bonus
                $player_points[$motm_id]['points'] += $bonus;
                $player_points[$motm_id]['breakdown']['bonus'] = $bonus;
            }
        }

        // 4. Save points to the database
        $this->db->query("INSERT INTO fantasy_player_points (player_id, gameweek, fixture_id, points, minutes_played, breakdown) VALUES (:player_id, :gameweek, :fixture_id, :points, :minutes_played, :breakdown)");
        foreach ($player_points as $player_id => $data) {
            if ($data['points'] === 0 && empty($data['breakdown']) && ($minutes_played[$player_id] ?? 0) === 0) continue;
            $this->db->bind(':player_id', $player_id);
            $this->db->bind(':gameweek', $gameweek);
            $this->db->bind(':fixture_id', $fixture_id);
            $this->db->bind(':points', $data['points']);
            $this->db->bind(':minutes_played', $minutes_played[$player_id] ?? 0);
            $this->db->bind(':breakdown', json_encode($data['breakdown']));
            $this->db->execute();
        }
    }

    private function _calculateMinutesPlayed($events, $home_lineup, $away_lineup) {
        $settings = $this->settingModel->getAll();
        $full_time = (int)($settings['full_time_duration'] ?? 90);

        $minutes = [];
        // Initialize starters for both teams
        foreach (array_keys($home_lineup->starters) as $player_id) {
            $minutes[$player_id] = $full_time;
        }
        foreach (array_keys($away_lineup->starters) as $player_id) {
            $minutes[$player_id] = $full_time;
        }

        // Process substitutions
        foreach ($events as $event) {
            if ($event->event_type === 'substitution') {
                $player_out_id = $event->player_id;
                $player_in_id = $event->assisted_by_player_id;

                // Player coming off
                if ($player_out_id && isset($minutes[$player_out_id])) {
                    $minutes[$player_out_id] = min($event->minute, $full_time);
                }
                // Player coming on
                if ($player_in_id) {
                    $minutes[$player_in_id] = max(0, $full_time - $event->minute);
                }
            }
        }
        return $minutes;
    }

    private function _updateFantasyTeamTotals($gameweek) {
        $this->db->query("SELECT id FROM fantasy_teams");
        $teams = $this->db->resultSet();
        
        // 1. Fetch all fixture statuses for this gameweek to check if matches are finished
        $this->db->query("SELECT id, home_team_id, away_team_id, status FROM fixtures WHERE gameweek = :gameweek");
        $this->db->bind(':gameweek', $gameweek);
        $fixtures = $this->db->resultSet();
        $team_fixture_status = [];
        foreach ($fixtures as $f) {
            $team_fixture_status[$f->home_team_id] = $f->status;
            $team_fixture_status[$f->away_team_id] = $f->status;
        }

        foreach ($teams as $team) {
            // Use fantasy_picks (snapshot) instead of fantasy_squads (current)
            $this->db->query("
                SELECT fp.id as pick_id, fp.player_id, fp.is_starter, fp.is_captain, fp.is_vice_captain, fp.bench_order, fp.is_auto_sub, p.position, p.team_id as real_team_id
                FROM fantasy_picks fp 
                JOIN players p ON fp.player_id = p.user_id
                WHERE fp.fantasy_team_id = :team_id AND fp.gameweek = :gameweek
            ");
            $this->db->bind(':team_id', $team->id);
            $this->db->bind(':gameweek', $gameweek);
            $squad_roster = $this->db->resultSet();
            
            if (empty($squad_roster)) {
                // If no picks found (e.g. missed deadline or error), skip
                continue;
            }

            $player_ids = array_map(fn($p) => $p->player_id, $squad_roster);
            $placeholders = implode(',', array_fill(0, count($player_ids), '?'));
            $this->db->query("
                SELECT player_id, SUM(points) as points, SUM(minutes_played) as minutes
                FROM fantasy_player_points WHERE gameweek = ? AND player_id IN ($placeholders) GROUP BY player_id
            ");
            $player_performances = $this->db->resultSet(array_merge([$gameweek], $player_ids));
            $performance_map = [];
            foreach ($player_performances as $perf) { $performance_map[$perf->player_id] = $perf; }

            $squad_with_data = [];
            foreach ($squad_roster as $player) {
                $player->points = $performance_map[$player->player_id]->points ?? 0;
                $player->minutes = $performance_map[$player->player_id]->minutes ?? 0;
                $squad_with_data[$player->player_id] = $player;
            }

            $starters = array_filter($squad_with_data, fn($p) => $p->is_starter);
            $bench = array_filter($squad_with_data, fn($p) => !$p->is_starter);
            usort($bench, fn($a, $b) => $a->bench_order <=> $b->bench_order);

            $captain = current(array_filter($squad_with_data, fn($p) => $p->is_captain)) ?: null;
            $vice_captain = current(array_filter($squad_with_data, fn($p) => $p->is_vice_captain)) ?: null;

            $final_starters = $starters;
            $lineup_changed = false;

            foreach ($starters as $starter_id => $starter_player) {
                // Check if starter played 0 minutes AND their match is finished
                $match_status = $team_fixture_status[$starter_player->real_team_id] ?? 'scheduled';
                
                if ($starter_player->minutes == 0 && in_array($match_status, ['finished', 'full_time'])) {
                    foreach ($bench as $bench_key => $bench_player) {
                        // Check if bench player played > 0 minutes
                        if ($bench_player->minutes > 0) {
                            $temp_lineup = $final_starters;
                            unset($temp_lineup[$starter_id]);
                            $temp_lineup[$bench_player->player_id] = $bench_player;
                            
                            $counts = ['Goalkeeper' => 0, 'Defender' => 0, 'Midfielder' => 0, 'Forward' => 0];
                            foreach ($temp_lineup as $p) { $counts[$p->position]++; }
                            
                            // Valid Formation Rules: 1 GK, Min 3 DEF, Min 2 MID, Min 1 FWD
                            // Strict GK rule: GK must replace GK
                            if ($counts['Goalkeeper'] === 1 && $counts['Defender'] >= 3 && $counts['Midfielder'] >= 2 && $counts['Forward'] >= 1) {
                                $final_starters = $temp_lineup;
                                unset($bench[$bench_key]);
                                
                                // Mark for DB update
                                $this->db->query("UPDATE fantasy_picks SET is_starter = 0 WHERE id = :id");
                                $this->db->bind(':id', $starter_player->pick_id);
                                $this->db->execute();
                                
                                $this->db->query("UPDATE fantasy_picks SET is_starter = 1, is_auto_sub = 1 WHERE id = :id");
                                $this->db->bind(':id', $bench_player->pick_id);
                                $this->db->execute();
                                
                                break;
                            }
                        }
                    }
                }
            }

            $gameweek_points = 0;
            foreach ($final_starters as $player) { $gameweek_points += $player->points; }

            $active_captain_id = null;
            if ($captain && isset($final_starters[$captain->player_id]) && $final_starters[$captain->player_id]->minutes > 0) {
                $active_captain_id = $captain->player_id;
            } elseif ($vice_captain && isset($final_starters[$vice_captain->player_id]) && $final_starters[$vice_captain->player_id]->minutes > 0) {
                $active_captain_id = $vice_captain->player_id;
            }
            if ($active_captain_id) { $gameweek_points += $squad_with_data[$active_captain_id]->points; }

            // 1. Insert/Update gameweek stats in the history table
            $this->db->query("INSERT INTO fantasy_team_gameweek_stats (fantasy_team_id, gameweek, points) VALUES (:team_id, :gameweek, :points) ON DUPLICATE KEY UPDATE points = :points_update");
            $this->db->bind(':team_id', $team->id);
            $this->db->bind(':gameweek', $gameweek);
            $this->db->bind(':points', $gameweek_points);
            $this->db->bind(':points_update', $gameweek_points);
            $this->db->execute();

            // 2. Update total points in fantasy_teams by summing up history
            $this->db->query("UPDATE fantasy_teams SET gameweek_points = :gw_points, total_points = (SELECT IFNULL(SUM(points), 0) FROM fantasy_team_gameweek_stats WHERE fantasy_team_id = :team_id_sum) WHERE id = :team_id");
            $this->db->bind(':gw_points', $gameweek_points);
            $this->db->bind(':team_id_sum', $team->id);
            $this->db->bind(':team_id', $team->id);
            $this->db->execute();
        }
    }

    /**
     * Gets the points breakdown for a specific player in a given gameweek.
     */
    public function getPointsBreakdownForPlayer($player_id, $gameweek) {
        $this->db->query("
            SELECT 
                fpp.points, 
                fpp.breakdown,
                f.home_team_score,
                f.away_team_score,
                ht.name as home_team_name,
                at.name as away_team_name
            FROM fantasy_player_points fpp
            JOIN fixtures f ON fpp.fixture_id = f.id
            JOIN teams ht ON f.home_team_id = ht.id
            JOIN teams at ON f.away_team_id = at.id
            WHERE fpp.player_id = :player_id AND fpp.gameweek = :gameweek
        ");
        $this->db->bind(':player_id', $player_id);
        $this->db->bind(':gameweek', $gameweek);
        return $this->db->resultSet();
    }

    /**
     * Gets the total points for a list of players for a specific gameweek.
     * @param array $player_ids An array of player user IDs.
     * @param int $gameweek The gameweek number.
     * @return array An associative array mapping player_id to total points.
     */
    public function getPointsForPlayers(array $player_ids, int $gameweek): array
    {
        if (empty($player_ids)) {
            return [];
        }
        $placeholders = implode(',', array_fill(0, count($player_ids), '?'));

        $this->db->query("
            SELECT player_id, SUM(points) as total_points
            FROM fantasy_player_points
            WHERE player_id IN ($placeholders) AND gameweek = ?
            GROUP BY player_id
        ");
        $results = $this->db->resultSet(array_merge($player_ids, [$gameweek]));
        return array_column($results, 'total_points', 'player_id');
    }

    /**
     * Gets a log of all fantasy points awarded in a given gameweek.
     * @param int $gameweek The gameweek number.
     * @return array An array of objects containing the fantasy point logs.
     */
    public function getFantasyPointsLog(int $gameweek): array
    {
        $this->db->query("
            SELECT
                fpp.player_id, u.first_name, u.last_name, p.position,
                fpp.fixture_id, f.home_team_id, f.away_team_id,
                SUM(fpp.points) as total_points, SUM(fpp.minutes_played) as total_minutes, fpp.breakdown
            FROM fantasy_player_points fpp
            JOIN users u ON fpp.player_id = u.id
            JOIN players p ON u.id = p.user_id
            JOIN fixtures f ON fpp.fixture_id = f.id
            WHERE fpp.gameweek = :gameweek
            GROUP BY fpp.player_id, fpp.fixture_id
            ORDER BY total_points DESC
        ");
        $this->db->bind(':gameweek', $gameweek);
        return $this->db->resultSet();
    }

    /**
     * Gets aggregated player stats for a specific gameweek (summing points from all fixtures in that GW).
     * @param int $gameweek
     * @return array
     */
    public function getGameweekPlayerStats(int $gameweek): array
    {
        $this->db->query("
            SELECT
                fpp.player_id, u.first_name, u.last_name, p.position, c.name as club_name,
                SUM(fpp.points) as total_points, SUM(fpp.minutes_played) as total_minutes
            FROM fantasy_player_points fpp
            JOIN users u ON fpp.player_id = u.id
            JOIN players p ON u.id = p.user_id
            JOIN teams t ON p.team_id = t.id
            JOIN clubs c ON t.club_id = c.id
            WHERE fpp.gameweek = :gameweek
            GROUP BY fpp.player_id
            ORDER BY total_points DESC, total_minutes DESC
        ");
        $this->db->bind(':gameweek', $gameweek);
        return $this->db->resultSet();
    }

    /**
     * Gets the form (average points over last 5 gameweeks) for all players in a league.
     * @param int $league_id
     * @param int $current_gameweek
     * @return array [player_id => form_value]
     */
    public function getPlayerForm($league_id, $current_gameweek) {
        $start_gw = max(1, $current_gameweek - 5);
        $end_gw = max(1, $current_gameweek - 1);
        
        if ($end_gw < $start_gw) return [];
        
        $divisor = $end_gw - $start_gw + 1;

        $this->db->query("
            SELECT player_id, SUM(points) as total_points
            FROM fantasy_player_points fpp
            JOIN fixtures f ON fpp.fixture_id = f.id
            WHERE f.league_id = :league_id AND fpp.gameweek BETWEEN :start_gw AND :end_gw
            GROUP BY player_id
        ");
        $this->db->bind(':league_id', $league_id);
        $this->db->bind(':start_gw', $start_gw);
        $this->db->bind(':end_gw', $end_gw);
        
        $results = $this->db->resultSet();
        $form = [];
        foreach ($results as $row) {
            $form[$row->player_id] = number_format($row->total_points / $divisor, 1);
        }
        return $form;
    }
}

?>