<?php

namespace App\Services;

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

final class AdvancedScoreSheetService
{
    private PDO $db;

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

    public function state(int $matchId): array
    {
        return [
            'rotations' => $this->rows('SELECT r.*, CONCAT(p.first_name, " ", p.last_name) player_name, t.name team_name
                FROM rotations r JOIN players p ON p.id = r.player_id JOIN teams t ON t.id = r.team_id
                WHERE r.match_id = :id ORDER BY r.set_number, r.team_id, r.position_number', $matchId),
            'liberos' => $this->rows('SELECT l.*, CONCAT(p.first_name, " ", p.last_name) player_name, t.name team_name
                FROM match_liberos l JOIN players p ON p.id = l.player_id JOIN teams t ON t.id = l.team_id
                WHERE l.match_id = :id ORDER BY l.set_number, l.team_id', $matchId),
            'substitutions' => $this->rows('SELECT s.*, outp.first_name out_first, outp.last_name out_last, inp.first_name in_first, inp.last_name in_last, t.name team_name
                FROM substitutions s JOIN players outp ON outp.id = s.player_out_id JOIN players inp ON inp.id = s.player_in_id JOIN teams t ON t.id = s.team_id
                WHERE s.match_id = :id ORDER BY s.id DESC', $matchId),
            'timeouts' => $this->rows('SELECT to1.*, t.name team_name FROM timeouts to1 JOIN teams t ON t.id = to1.team_id
                WHERE to1.match_id = :id ORDER BY to1.id DESC', $matchId),
            'rallies' => $this->rows('SELECT r.*, wt.name winning_team, st.name serving_team
                FROM rally_history r LEFT JOIN teams wt ON wt.id = r.winning_team_id LEFT JOIN teams st ON st.id = r.serving_team_id
                WHERE r.match_id = :id ORDER BY r.id DESC LIMIT 100', $matchId),
            'audit' => $this->rows('SELECT a.*, u.name user_name FROM referee_audit_logs a LEFT JOIN users u ON u.id = a.user_id
                WHERE a.match_id = :id ORDER BY a.id DESC LIMIT 100', $matchId),
            'signatures' => $this->rows('SELECT * FROM referee_signatures WHERE match_id = :id ORDER BY signed_at DESC', $matchId),
        ];
    }

    public function setRotation(int $matchId, array $data, array $user): array
    {
        $set = $this->currentSet($matchId);
        $teamId = $this->validTeam($matchId, (int) $data['team_id']);
        $position = (int) $data['position_number'];
        if ($position < 1 || $position > 6) {
            throw new InvalidArgumentException('La posicion debe estar entre 1 y 6');
        }
        $this->ensurePlayerInTeam((int) $data['player_id'], $teamId);

        $stmt = $this->db->prepare(
            'INSERT INTO rotations (match_id, team_id, set_number, position_number, player_id, is_libero)
             VALUES (:match_id, :team_id, :set_number, :position_number, :player_id, :is_libero)
             ON DUPLICATE KEY UPDATE player_id = VALUES(player_id), is_libero = VALUES(is_libero)'
        );
        $stmt->execute([
            'match_id' => $matchId,
            'team_id' => $teamId,
            'set_number' => $data['set_number'] ?? $set['set_number'],
            'position_number' => $position,
            'player_id' => (int) $data['player_id'],
            'is_libero' => (int) ($data['is_libero'] ?? 0),
        ]);

        $this->event($matchId, 'rotation', $teamId, $data['player_id'], "Rotacion posicion $position", $user);
        $this->audit($matchId, 'rotation.set', $data, $user);
        return $this->state($matchId);
    }

    public function setLibero(int $matchId, array $data, array $user): array
    {
        $set = $this->currentSet($matchId);
        $teamId = $this->validTeam($matchId, (int) $data['team_id']);
        $this->ensurePlayerInTeam((int) $data['player_id'], $teamId);

        $stmt = $this->db->prepare(
            'INSERT IGNORE INTO match_liberos (match_id, team_id, player_id, set_number, is_starting)
             VALUES (:match_id, :team_id, :player_id, :set_number, :is_starting)'
        );
        $stmt->execute([
            'match_id' => $matchId,
            'team_id' => $teamId,
            'player_id' => (int) $data['player_id'],
            'set_number' => $data['set_number'] ?? $set['set_number'],
            'is_starting' => (int) ($data['is_starting'] ?? 0),
        ]);

        $this->event($matchId, 'libero', $teamId, $data['player_id'], 'Registro de libero', $user);
        $this->audit($matchId, 'libero.set', $data, $user);
        return $this->state($matchId);
    }

    public function substitute(int $matchId, array $data, array $user): array
    {
        $set = $this->currentSet($matchId);
        $teamId = $this->validTeam($matchId, (int) $data['team_id']);
        $setNumber = (int) ($data['set_number'] ?? $set['set_number']);
        $this->ensurePlayerInTeam((int) $data['player_out_id'], $teamId);
        $this->ensurePlayerInTeam((int) $data['player_in_id'], $teamId);
        if ((int) $data['player_out_id'] === (int) $data['player_in_id']) {
            throw new InvalidArgumentException('Los jugadores de entrada y salida deben ser distintos');
        }
        if ($this->countRows('substitutions', $matchId, $teamId, $setNumber) >= 6) {
            throw new InvalidArgumentException('Maximo reglamentario de 6 sustituciones por set');
        }

        $stmt = $this->db->prepare(
            'INSERT INTO substitutions (match_id, team_id, set_number, player_out_id, player_in_id, reason, created_by)
             VALUES (:match_id, :team_id, :set_number, :player_out_id, :player_in_id, :reason, :created_by)'
        );
        $stmt->execute([
            'match_id' => $matchId,
            'team_id' => $teamId,
            'set_number' => $setNumber,
            'player_out_id' => (int) $data['player_out_id'],
            'player_in_id' => (int) $data['player_in_id'],
            'reason' => $data['reason'] ?? null,
            'created_by' => $user['sub'] ?? null,
        ]);

        $this->event($matchId, 'substitution', $teamId, $data['player_in_id'], 'Sustitucion reglamentaria', $user);
        $this->audit($matchId, 'substitution.create', $data, $user);
        return $this->state($matchId);
    }

    public function timeout(int $matchId, array $data, array $user): array
    {
        $set = $this->currentSet($matchId);
        $teamId = $this->validTeam($matchId, (int) $data['team_id']);
        $setNumber = (int) ($data['set_number'] ?? $set['set_number']);
        if ($this->countRows('timeouts', $matchId, $teamId, $setNumber) >= 2) {
            throw new InvalidArgumentException('Maximo reglamentario de 2 tiempos por set');
        }

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

        $this->event($matchId, 'timeout', $teamId, null, 'Tiempo solicitado', $user);
        $this->audit($matchId, 'timeout.create', $data, $user);
        return $this->state($matchId);
    }

    public function rally(int $matchId, array $data, array $user): array
    {
        $set = $this->currentSet($matchId);
        $winningTeamId = isset($data['winning_team_id']) ? $this->validTeam($matchId, (int) $data['winning_team_id']) : null;
        $servingTeamId = isset($data['serving_team_id']) ? $this->validTeam($matchId, (int) $data['serving_team_id']) : null;
        $rallyNumber = $this->nextRallyNumber($matchId, (int) $set['set_number']);

        $stmt = $this->db->prepare(
            'INSERT INTO rally_history
             (match_id, set_number, rally_number, serving_team_id, winning_team_id, result_type, points_home, points_away, notes, created_by)
             VALUES (:match_id, :set_number, :rally_number, :serving_team_id, :winning_team_id, :result_type, :points_home, :points_away, :notes, :created_by)'
        );
        $stmt->execute([
            'match_id' => $matchId,
            'set_number' => $set['set_number'],
            'rally_number' => $rallyNumber,
            'serving_team_id' => $servingTeamId,
            'winning_team_id' => $winningTeamId,
            'result_type' => $data['result_type'] ?? 'point',
            'points_home' => $set['home_points'],
            'points_away' => $set['away_points'],
            'notes' => $data['notes'] ?? null,
            'created_by' => $user['sub'] ?? null,
        ]);

        $this->audit($matchId, 'rally.create', $data, $user);
        return $this->state($matchId);
    }

    public function sanction(int $matchId, array $data, array $user): array
    {
        $teamId = !empty($data['team_id']) ? $this->validTeam($matchId, (int) $data['team_id']) : null;
        $playerId = !empty($data['player_id']) ? (int) $data['player_id'] : null;
        $card = $data['card_type'] ?? 'yellow';
        if (!in_array($card, ['yellow', 'red'], true)) {
            throw new InvalidArgumentException('Tarjeta invalida');
        }

        $stmt = $this->db->prepare(
            'INSERT INTO sanctions (match_id, team_id, player_id, card_type, reason)
             VALUES (:match_id, :team_id, :player_id, :card_type, :reason)'
        );
        $stmt->execute([
            'match_id' => $matchId,
            'team_id' => $teamId,
            'player_id' => $playerId,
            'card_type' => $card,
            'reason' => $data['reason'] ?? null,
        ]);

        $this->event($matchId, $card === 'red' ? 'red_card' : 'yellow_card', $teamId, $playerId, $data['reason'] ?? 'Sancion', $user);
        $this->audit($matchId, 'sanction.create', $data, $user);
        return $this->state($matchId);
    }

    public function sign(int $matchId, array $data, array $user): array
    {
        $payload = [
            'match_id' => $matchId,
            'signer_name' => $data['signer_name'],
            'role' => $data['role'] ?? 'principal',
            'user_id' => $user['sub'] ?? null,
            'signed_at' => date('c'),
        ];
        $hash = hash('sha256', json_encode($payload));
        $stmt = $this->db->prepare(
            'INSERT INTO referee_signatures (match_id, referee_id, signer_name, role, signature_hash, signed_payload)
             VALUES (:match_id, :referee_id, :signer_name, :role, :signature_hash, :signed_payload)
             ON DUPLICATE KEY UPDATE signer_name = VALUES(signer_name), signature_hash = VALUES(signature_hash), signed_payload = VALUES(signed_payload), signed_at = CURRENT_TIMESTAMP'
        );
        $stmt->execute([
            'match_id' => $matchId,
            'referee_id' => $data['referee_id'] ?? null,
            'signer_name' => $data['signer_name'],
            'role' => $payload['role'],
            'signature_hash' => $hash,
            'signed_payload' => json_encode($payload),
        ]);

        $this->event($matchId, 'signature', null, null, 'Firma digital arbitral', $user);
        $this->audit($matchId, 'signature.create', ['hash' => $hash] + $data, $user);
        return ['signature_hash' => $hash, 'advanced' => $this->state($matchId)];
    }

    private function event(int $matchId, string $type, ?int $teamId, mixed $playerId, string $notes, array $user): void
    {
        $set = $this->currentSet($matchId);
        $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,
            'player_id' => $playerId ?: null,
            'event_type' => $type,
            'points_home' => $set['home_points'],
            'points_away' => $set['away_points'],
            'notes' => $notes,
            'created_by' => $user['sub'] ?? null,
        ]);
    }

    private function audit(int $matchId, string $action, array $payload, array $user): void
    {
        $stmt = $this->db->prepare('INSERT INTO referee_audit_logs (match_id, user_id, action, payload) VALUES (:match_id, :user_id, :action, :payload)');
        $stmt->execute([
            'match_id' => $matchId,
            'user_id' => $user['sub'] ?? null,
            'action' => $action,
            'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        ]);
    }

    private function validTeam(int $matchId, int $teamId): int
    {
        $match = $this->match($matchId);
        if (!in_array($teamId, [(int) $match['home_team_id'], (int) $match['away_team_id']], true)) {
            throw new InvalidArgumentException('El equipo no participa en este partido');
        }
        return $teamId;
    }

    private function ensurePlayerInTeam(int $playerId, int $teamId): void
    {
        $stmt = $this->db->prepare('SELECT id FROM players WHERE id = :id AND team_id = :team_id');
        $stmt->execute(['id' => $playerId, 'team_id' => $teamId]);
        if (!$stmt->fetch()) {
            throw new InvalidArgumentException('El jugador no pertenece al equipo');
        }
    }

    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]);
        $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE id = :id');
        $stmt->execute(['id' => $this->db->lastInsertId()]);
        return $stmt->fetch();
    }

    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 countRows(string $table, int $matchId, int $teamId, int $setNumber): int
    {
        $stmt = $this->db->prepare("SELECT COUNT(*) total FROM $table WHERE match_id = :match_id AND team_id = :team_id AND set_number = :set_number");
        $stmt->execute(['match_id' => $matchId, 'team_id' => $teamId, 'set_number' => $setNumber]);
        return (int) $stmt->fetch()['total'];
    }

    private function nextRallyNumber(int $matchId, int $setNumber): int
    {
        $stmt = $this->db->prepare('SELECT COALESCE(MAX(rally_number), 0) + 1 next_number FROM rally_history WHERE match_id = :match_id AND set_number = :set_number');
        $stmt->execute(['match_id' => $matchId, 'set_number' => $setNumber]);
        return (int) $stmt->fetch()['next_number'];
    }

    private function rows(string $sql, int $matchId): array
    {
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['id' => $matchId]);
        return $stmt->fetchAll();
    }
}
