<?php
/**
 * DisciplineSettingsModel.php
 * 
 * Model dla ustawień dyscyplin (Ping-Pong, Papier-Kamień-Nożyce, Piłkarzyki)
 * Obsługuje versioning, validację i trwałość danych
 */

class DisciplineSettingsModel
{
    private $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
        $this->ensureTableExists();
    }

    /**
     * Upewnia się, że tabela settings_disciplines istnieje
     */
    private function ensureTableExists()
    {
        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS settings_disciplines (
                id INT AUTO_INCREMENT PRIMARY KEY,
                discipline VARCHAR(50) NOT NULL UNIQUE,
                
                -- Reguły gry (logika)
                pointsToWin INT NOT NULL DEFAULT 10,
                setsToWin INT NOT NULL DEFAULT 2,
                serveRotation INT NOT NULL DEFAULT 2,
                specialRules TEXT,
                
                -- Personalizacja UI (nie wpływa na logiką gry)
                customization JSON,
                
                -- Versioning ustawień
                settingsVersion INT NOT NULL DEFAULT 1,
                
                -- Metadane
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                updated_by INT,
                
                INDEX idx_discipline (discipline),
                INDEX idx_version (settingsVersion)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
        ");
    }

    /**
     * Pobiera ustawienia dla dyscypliny
     * 
     * @param string $discipline Nazwa dyscypliny (np. 'ping-pong')
     * @return array|null Ustawienia lub null jeśli nie istnieją
     */
    public function getSettings($discipline)
    {
        $stmt = $this->pdo->prepare("
            SELECT * FROM settings_disciplines 
            WHERE discipline = :discipline
            LIMIT 1
        ");
        $stmt->execute([':discipline' => $discipline]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            return null;
        }

        // Rzutuj INT kolumny
        $row['pointsToWin'] = (int)$row['pointsToWin'];
        $row['setsToWin'] = (int)$row['setsToWin'];
        $row['serveRotation'] = (int)$row['serveRotation'];
        $row['settingsVersion'] = (int)$row['settingsVersion'];
        $row['updated_by'] = $row['updated_by'] ? (int)$row['updated_by'] : null;

        // Dekoduj JSON fields
        if (!empty($row['customization'])) {
            $row['customization'] = json_decode($row['customization'], true);
        }

        return $row;
    }

    /**
     * Pobiera ustawienia z określonej wersji
     * (do snapshot'ów przy starcie meczu)
     * 
     * @param string $discipline Nazwa dyscypliny
     * @param int $version Numer wersji
     * @return array|null Ustawienia danej wersji
     */
    public function getSettingsByVersion($discipline, $version)
    {
        // TODO: W przyszłości można dodać tabelę settings_disciplines_history
        // dla pełnej historii zmian
        $stmt = $this->pdo->prepare("
            SELECT * FROM settings_disciplines 
            WHERE discipline = :discipline 
            AND settingsVersion = :version
            LIMIT 1
        ");
        $stmt->execute([
            ':discipline' => $discipline,
            ':version' => (int)$version
        ]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            return null;
        }

        // Rzutuj INT kolumny
        $row['pointsToWin'] = (int)$row['pointsToWin'];
        $row['setsToWin'] = (int)$row['setsToWin'];
        $row['serveRotation'] = (int)$row['serveRotation'];
        $row['settingsVersion'] = (int)$row['settingsVersion'];
        $row['updated_by'] = $row['updated_by'] ? (int)$row['updated_by'] : null;

        if (!empty($row['customization'])) {
            $row['customization'] = json_decode($row['customization'], true);
        }

        return $row;
    }

    /**
     * Aktualizuje ustawienia dla dyscypliny
     * Automatycznie zwiększa versioning
     * 
     * @param string $discipline Nazwa dyscypliny
     * @param array $settings Nowe ustawienia
     * @param int $userId ID użytkownika wykonującego zmianę
     * @return array Zaktualizowane ustawienia
     * @throws Exception
     */
    public function updateSettings($discipline, array $settings, $userId)
    {
        // Waliduj dane
        $this->validateSettingsInput($settings);

        // Pobierz obecne ustawienia, aby zwiększyć versioning
        $current = $this->getSettings($discipline);
        $newVersion = ($current ? (int)$current['settingsVersion'] + 1 : 1);

        // Przygotuj dane do insertu/update
        $data = [
            ':discipline' => $discipline,
            ':pointsToWin' => (int)$settings['pointsToWin'],
            ':setsToWin' => (int)$settings['setsToWin'],
            ':serveRotation' => (int)$settings['serveRotation'],
            ':specialRules' => $settings['specialRules'] ?? null,
            ':customization' => !empty($settings['customization']) 
                ? json_encode($settings['customization'], JSON_UNESCAPED_UNICODE) 
                : null,
            ':settingsVersion' => $newVersion,
            ':updated_by' => $userId
        ];

        $this->pdo->beginTransaction();
        try {
            if ($current) {
                // UPDATE
                $stmt = $this->pdo->prepare("
                    UPDATE settings_disciplines SET
                        pointsToWin = :pointsToWin,
                        setsToWin = :setsToWin,
                        serveRotation = :serveRotation,
                        specialRules = :specialRules,
                        customization = :customization,
                        settingsVersion = :settingsVersion,
                        updated_by = :updated_by,
                        updated_at = NOW()
                    WHERE discipline = :discipline
                ");
            } else {
                // INSERT (nowa dyscyplina)
                $stmt = $this->pdo->prepare("
                    INSERT INTO settings_disciplines (
                        discipline,
                        pointsToWin,
                        setsToWin,
                        serveRotation,
                        specialRules,
                        customization,
                        settingsVersion,
                        updated_by
                    ) VALUES (
                        :discipline,
                        :pointsToWin,
                        :setsToWin,
                        :serveRotation,
                        :specialRules,
                        :customization,
                        :settingsVersion,
                        :updated_by
                    )
                ");
            }

            $stmt->execute($data);

            $result = $this->getSettings($discipline);
            $this->pdo->commit();

            return $result;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    /**
     * Waliduje dane wejściowe ustawień
     * 
     * @param array $settings Ustawienia do walidacji
     * @throws InvalidArgumentException
     */
    private function validateSettingsInput(array $settings)
    {
        $errors = [];

        // Walidacja pointsToWin
        if (!isset($settings['pointsToWin'])) {
            $errors[] = 'pointsToWin is required';
        } else {
            $ptw = (int)$settings['pointsToWin'];
            if ($ptw < 1 || $ptw > 100) {
                $errors[] = 'pointsToWin must be between 1 and 100';
            }
        }

        // Walidacja setsToWin
        if (!isset($settings['setsToWin'])) {
            $errors[] = 'setsToWin is required';
        } else {
            $stw = (int)$settings['setsToWin'];
            if ($stw < 1 || $stw > 100) {
                $errors[] = 'setsToWin must be between 1 and 100';
            }
        }

        // Walidacja serveRotation
        if (!isset($settings['serveRotation'])) {
            $errors[] = 'serveRotation is required';
        } else {
            $sr = (int)$settings['serveRotation'];
            if ($sr < 1 || $sr > 50) {
                $errors[] = 'serveRotation must be between 1 and 50';
            }
        }

        // Walidacja specialRules (opcjonalne, ale jeśli podane to string)
        if (isset($settings['specialRules']) && !is_string($settings['specialRules'])) {
            $errors[] = 'specialRules must be a string';
        }

        // Walidacja customization (opcjonalne, ale jeśli podane to musi być array/object)
        if (isset($settings['customization'])) {
            if (!is_array($settings['customization']) && !is_object($settings['customization'])) {
                $errors[] = 'customization must be an object/array';
            }
        }

        // Logika biznesowa
        // Remis - wymuszenie override reguł
        if (isset($settings['pointsToWin']) && isset($settings['setsToWin'])) {
            $ptw = (int)$settings['pointsToWin'];
            $stw = (int)$settings['setsToWin'];
            
            // Jeśli oba są parzyste, możliwy jest remis - lepiej wymusić nieparzyste
            // W przypadku remisu w ostatnim secie, gracze muszą grać dalej
            if ($ptw % 2 === 0 || $stw % 2 === 0) {
                $errors[] = 'pointsToWin and setsToWin should be odd numbers to avoid draws in final set';
            }
        }

        if (!empty($errors)) {
            throw new InvalidArgumentException(implode('; ', $errors));
        }
    }

    /**
     * Zwraca domyślne ustawienia dla dyscypliny
     * 
     * @param string $discipline Nazwa dyscypliny
     * @return array Domyślne ustawienia
     */
    public static function getDefaults($discipline = 'ping-pong')
    {
        $defaults = [
            'ping-pong' => [
                'pointsToWin' => 11,
                'setsToWin' => 3,
                'serveRotation' => 2,
                'specialRules' => 'Deuce at 10-10 (play until 2 points ahead)',
                'customization' => [
                    'tableColor' => '#2d5016',
                    'ballColor' => '#ff6600',
                    'paddleColor' => '#000000',
                    'uiTheme' => 'dark'
                ]
            ],
            'rock-paper-scissors' => [
                'pointsToWin' => 5,
                'setsToWin' => 1,
                'serveRotation' => 1,
                'specialRules' => 'Best of 1, instant rounds',
                'customization' => [
                    'animationSpeed' => 'fast',
                    'uiTheme' => 'light'
                ]
            ],
            'table-football' => [
                'pointsToWin' => 5,
                'setsToWin' => 1,
                'serveRotation' => 3,
                'specialRules' => 'Standard foosball rules, auto-restart after goal',
                'customization' => [
                    'tableColor' => '#000000',
                    'figureColor' => '#ffffff',
                    'uiTheme' => 'dark'
                ]
            ]
        ];

        return $defaults[$discipline] ?? $defaults['ping-pong'];
    }

    /**
     * Inicjalizuje ustawienia dla dyscypliny (jeśli nie istnieją)
     * 
     * @param string $discipline Nazwa dyscypliny
     * @param int $userId ID administratora
     */
    public function initializeIfNotExists($discipline, $userId)
    {
        if (!$this->getSettings($discipline)) {
            $defaults = self::getDefaults($discipline);
            $this->updateSettings($discipline, $defaults, $userId);
        }
    }

    /**
     * Pobiera snapshot ustawień dla meczu
     * (snapshot to kopia ustawień w momencie startu meczu)
     * 
     * @param string $discipline Nazwa dyscypliny
     * @param int $version Opcjonalnie: wersja ustawień. Jeśli null, bierze najnowsze.
     * @return array Snapshot do zapisania w meczu
     */
    public function getSnapshot($discipline, $version = null)
    {
        if ($version !== null) {
            $settings = $this->getSettingsByVersion($discipline, (int)$version);
        } else {
            $settings = $this->getSettings($discipline);
        }

        if (!$settings) {
            throw new RuntimeException("Settings not found for discipline: $discipline");
        }

        return [
            'discipline' => $discipline,
            'settingsVersion' => (int)$settings['settingsVersion'],
            'rules' => [
                'pointsToWin' => (int)$settings['pointsToWin'],
                'setsToWin' => (int)$settings['setsToWin'],
                'serveRotation' => (int)$settings['serveRotation'],
                'specialRules' => $settings['specialRules']
            ],
            'snapshotTimestamp' => $settings['updated_at']
        ];
    }

    /**
     * Pobiera historię zmian dla dyscypliny
     * (przydatne do debuggowania i audytu)
     * 
     * @param string $discipline Nazwa dyscypliny
     * @return array Historia
     */
    public function getHistory($discipline)
    {
        // TODO: W przyszłości należy dodać tabelę settings_disciplines_history
        // Po prostu zwracamy obecne dane z metadata
        $current = $this->getSettings($discipline);

        if (!$current) {
            return [];
        }

        return [
            [
                'version' => $current['settingsVersion'],
                'updated_at' => $current['updated_at'],
                'updated_by' => $current['updated_by'],
                'changes' => 'Latest version'
            ]
        ];
    }

    /**
     * Czyści ustawienia dyscypliny z bazy (dla testów)
     * 
     * @param string $discipline Nazwa dyscypliny do usunięcia
     * @return bool True jeśli usunięto
     */
    public function deleteSettings($discipline)
    {
        try {
            $stmt = $this->pdo->prepare("DELETE FROM settings_disciplines WHERE discipline = ?");
            return $stmt->execute([$discipline]);
        } catch (Exception $e) {
            return false;
        }
    }
}
?>
