<?php

namespace App\Services;

use App\Core\Database;
use InvalidArgumentException;
use PDO;

final class ScoreSheetService
{
    private PDO $db;

    public function __construct()
    {
        $this->db = Database::connection();
    }

    public function state(int $matchId): array
    {
        $match = $this->match($matchId);
        $sets = $this->sets($matchId);
        $events = $this->events($matchId);

        return [
            'match' => $match,
            'sets' => $sets,
            'sets_won' => [
                'home' => count(array_filter($sets, fn ($set) => ($set['winner_team_id'] ?? null) === $match['home_team_id'])),
                'away' => count(array_filter($sets, fn ($set) => ($set['winner_team_id'] ?? null) === $match['away_team_id'])),
            ],
            'events' => $events,
        ];
    }

    public function addEvent(int $matchId, array $data, array $user): array
    {
        $match = $this->match($matchId);
        if (($match['status'] ?? '') === 'finished') {
            throw new InvalidArgumentException('El partido ya finalizó');
        }

        $type = $data['event_type'] ?? 'point';
        $teamId = (int) ($data['team_id'] ?? 0);
        if ($teamId && !in_array($teamId, [(int) $match['home_team_id'], (int) $match['away_team_id']], true)) {
            throw new InvalidArgumentException('El equipo no participa en este partido');
        }

        $this->db->beginTransaction();
        try {
            $set = $this->currentSet($matchId);
            if ($type === 'point') {
                $homePoint = $teamId === (int) $match['home_team_id'] ? 1 : 0;
                $awayPoint = $teamId === (int) $match['away_team_id'] ? 1 : 0;
                $stmt = $this->db->prepare(
                    'UPDATE match_sets
                     SET home_points = home_points + :home, away_points = away_points + :away
                     WHERE id = :id'
                );
                $stmt->execute(['home' => $homePoint, 'away' => $awayPoint, 'id' => $set['id']]);
                $set = $this->setById((int) $set['id']);
            }

            $stmt = $this->db->prepare(
                'INSERT INTO match_events
                 (match_id, set_number, team_id, player_id, event_type, points_home, points_away, notes, created_by)
                 VALUES (:match_id, :set_number, :team_id, :player_id, :event_type, :points_home, :points_away, :notes, :created_by)'
            );
            $stmt->execute([
                'match_id' => $matchId,
                'set_number' => $set['set_number'],
                'team_id' => $teamId ?: null,
                'player_id' => $data['player_id'] ?? null,
                'event_type' => $type,
                'points_home' => $set['home_points'],
                'points_away' => $set['away_points'],
                'notes' => $data['notes'] ?? null,
                'created_by' => $user['sub'] ?? null,
            ]);

            $this->evaluateSetAndMatch($matchId, $match, $set);
            $this->db->commit();
        } catch (\Throwable $e) {
            $this->db->rollBack();
            throw $e;
        }

        return $this->state($matchId);
    }

    public function standings(int $tournamentId): array
    {
        $stmt = $this->db->prepare(
            'SELECT t.id, t.name,
                COALESCE(s.played, 0) played,
                COALESCE(s.won, 0) won,
                COALESCE(s.lost, 0) lost,
                COALESCE(s.sets_for, 0) sets_for,
                COALESCE(s.sets_against, 0) sets_against,
                COALESCE(s.points, 0) points
             FROM teams t
             LEFT JOIN team_standings s ON s.team_id = t.id
             WHERE t.tournament_id = :tournament_id
             ORDER BY points DESC, (sets_for - sets_against) DESC, sets_for DESC, t.name ASC'
        );
        $stmt->execute(['tournament_id' => $tournamentId]);
        return $stmt->fetchAll();
    }

    public function stats(int $tournamentId): array
    {
        $stmt = $this->db->prepare(
            'SELECT p.id, CONCAT(p.first_name, " ", p.last_name) AS player_name, tm.name AS team_name,
                SUM(e.event_type = "point") AS points,
                SUM(e.event_type = "ace") AS aces,
                SUM(e.event_type = "block") AS blocks,
                SUM(e.event_type = "attack") AS attacks,
                SUM(e.event_type = "mvp") AS mvp
             FROM players p
             JOIN teams tm ON tm.id = p.team_id
             LEFT JOIN match_events e ON e.player_id = p.id
             WHERE tm.tournament_id = :tournament_id
             GROUP BY p.id, player_name, team_name
             ORDER BY points DESC, aces DESC, blocks DESC
             LIMIT 50'
        );
        $stmt->execute(['tournament_id' => $tournamentId]);
        return ['players' => $stmt->fetchAll(), 'teams' => $this->standings($tournamentId)];
    }

    private function evaluateSetAndMatch(int $matchId, array $match, array $set): void
    {
        $target = (int) $set['set_number'] === 5 ? 15 : 25;
        $home = (int) $set['home_points'];
        $away = (int) $set['away_points'];
        if (max($home, $away) < $target || abs($home - $away) < 2) {
            $this->markLive($matchId);
            return;
        }

        $winner = $home > $away ? $match['home_team_id'] : $match['away_team_id'];
        $stmt = $this->db->prepare('UPDATE match_sets SET winner_team_id = :winner WHERE id = :id');
        $stmt->execute(['winner' => $winner, 'id' => $set['id']]);

        $sets = $this->sets($matchId);
        $homeSets = count(array_filter($sets, fn ($row) => ($row['winner_team_id'] ?? null) === $match['home_team_id']));
        $awaySets = count(array_filter($sets, fn ($row) => ($row['winner_team_id'] ?? null) === $match['away_team_id']));
        if ($homeSets === 3 || $awaySets === 3) {
            $winnerTeam = $homeSets === 3 ? $match['home_team_id'] : $match['away_team_id'];
            $this->finishMatch($matchId, (int) $winnerTeam, $homeSets, $awaySets);
            return;
        }

        $nextNumber = (int) $set['set_number'] + 1;
        $sideSwitch = $nextNumber % 2 === 0 ? 'switched' : 'normal';
        $stmt = $this->db->prepare(
            'INSERT IGNORE INTO match_sets (match_id, set_number, home_points, away_points, side_state)
             VALUES (:match_id, :set_number, 0, 0, :side_state)'
        );
        $stmt->execute(['match_id' => $matchId, 'set_number' => $nextNumber, 'side_state' => $sideSwitch]);
    }

    private function finishMatch(int $matchId, int $winnerTeamId, int $homeSets, int $awaySets): void
    {
        $stmt = $this->db->prepare(
            'UPDATE matches SET status = "finished", winner_team_id = :winner, home_sets = :home_sets, away_sets = :away_sets WHERE id = :id'
        );
        $stmt->execute(['winner' => $winnerTeamId, 'home_sets' => $homeSets, 'away_sets' => $awaySets, 'id' => $matchId]);
        $this->rebuildStandings((int) $this->match($matchId)['tournament_id']);
    }

    public function rebuildStandings(int $tournamentId): void
    {
        $this->db->prepare('DELETE FROM team_standings WHERE tournament_id = :id')->execute(['id' => $tournamentId]);
        $teams = $this->db->prepare('SELECT id FROM teams WHERE tournament_id = :id');
        $teams->execute(['id' => $tournamentId]);
        foreach ($teams->fetchAll() as $team) {
            $stmt = $this->db->prepare(
                'SELECT
                    SUM((home_team_id = :team OR away_team_id = :team) AND status = "finished") played,
                    SUM(winner_team_id = :team) won,
                    SUM(status = "finished" AND winner_team_id <> :team) lost,
                    SUM(CASE WHEN home_team_id = :team THEN home_sets WHEN away_team_id = :team THEN away_sets ELSE 0 END) sets_for,
                    SUM(CASE WHEN home_team_id = :team THEN away_sets WHEN away_team_id = :team THEN home_sets ELSE 0 END) sets_against
                 FROM matches WHERE tournament_id = :tournament_id'
            );
            $stmt->execute(['team' => $team['id'], 'tournament_id' => $tournamentId]);
            $row = $stmt->fetch();
            $won = (int) ($row['won'] ?? 0);
            $lost = (int) ($row['lost'] ?? 0);
            $this->db->prepare(
                'INSERT INTO team_standings (tournament_id, team_id, played, won, lost, sets_for, sets_against, points)
                 VALUES (:tournament_id, :team_id, :played, :won, :lost, :sets_for, :sets_against, :points)'
            )->execute([
                'tournament_id' => $tournamentId,
                'team_id' => $team['id'],
                'played' => (int) ($row['played'] ?? 0),
                'won' => $won,
                'lost' => $lost,
                'sets_for' => (int) ($row['sets_for'] ?? 0),
                'sets_against' => (int) ($row['sets_against'] ?? 0),
                'points' => ($won * 3) + $lost,
            ]);
        }
    }

    private function currentSet(int $matchId): array
    {
        $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE match_id = :id AND winner_team_id IS NULL ORDER BY set_number LIMIT 1');
        $stmt->execute(['id' => $matchId]);
        $set = $stmt->fetch();
        if ($set) {
            return $set;
        }
        $this->db->prepare('INSERT INTO match_sets (match_id, set_number) VALUES (:id, 1)')->execute(['id' => $matchId]);
        return $this->setById((int) $this->db->lastInsertId());
    }

    private function markLive(int $matchId): void
    {
        $this->db->prepare('UPDATE matches SET status = "live" WHERE id = :id AND status = "scheduled"')->execute(['id' => $matchId]);
    }

    private function match(int $matchId): array
    {
        $stmt = $this->db->prepare('SELECT * FROM matches WHERE id = :id');
        $stmt->execute(['id' => $matchId]);
        $match = $stmt->fetch();
        if (!$match) {
            throw new InvalidArgumentException('Partido no encontrado');
        }
        return $match;
    }

    private function setById(int $id): array
    {
        $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE id = :id');
        $stmt->execute(['id' => $id]);
        return $stmt->fetch();
    }

    private function sets(int $matchId): array
    {
        $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE match_id = :id ORDER BY set_number');
        $stmt->execute(['id' => $matchId]);
        return $stmt->fetchAll();
    }

    private function events(int $matchId): array
    {
        $stmt = $this->db->prepare(
            'SELECT e.*, CONCAT(p.first_name, " ", p.last_name) AS player_name, t.name AS team_name
             FROM match_events e
             LEFT JOIN players p ON p.id = e.player_id
             LEFT JOIN teams t ON t.id = e.team_id
             WHERE e.match_id = :id
             ORDER BY e.id DESC LIMIT 100'
        );
        $stmt->execute(['id' => $matchId]);
        return $stmt->fetchAll();
    }
}
