<?php
/**
 * EasyStream OAuth 2.0 Server
 * OAuth 2.0 authentication for API
 * Version: 1.0
 */

defined('_ISVALID') or header('Location: /error');

class VOAuth {
    private static $db;

    public static function init() {
        self::$db = VDatabase::getInstance();
    }

    /**
     * Generate authorization code
     * @param int $usr_id User ID
     * @param string $client_id Client ID
     * @param array $scopes Requested scopes
     * @param string $redirect_uri Redirect URI
     * @return string Authorization code
     */
    public static function generateAuthCode($usr_id, $client_id, $scopes, $redirect_uri) {
        self::init();

        $code = bin2hex(random_bytes(32));
        $usr_id = (int)$usr_id;
        $client_id_safe = VDatabase::escape($client_id);
        $scopes_json = VDatabase::escape(json_encode($scopes));
        $redirect_safe = VDatabase::escape($redirect_uri);

        $sql = "INSERT INTO db_oauth_codes
                (usr_id, client_id, code, scopes, redirect_uri, expires_at, created_at)
                VALUES ($usr_id, '$client_id_safe', '$code', '$scopes_json', '$redirect_safe', DATE_ADD(NOW(), INTERVAL 10 MINUTE), NOW())";

        if (self::$db->execute($sql)) {
            return $code;
        }

        return false;
    }

    /**
     * Exchange authorization code for access token
     * @param string $code Authorization code
     * @param string $client_id Client ID
     * @param string $client_secret Client secret
     * @return array|false Token data or false
     */
    public static function exchangeCode($code, $client_id, $client_secret) {
        self::init();

        $code_safe = VDatabase::escape($code);
        $client_id_safe = VDatabase::escape($client_id);

        // Validate code
        $sql = "SELECT * FROM db_oauth_codes
                WHERE code = '$code_safe'
                AND client_id = '$client_id_safe'
                AND expires_at > NOW()
                AND is_used = 0";

        $result = self::$db->execute($sql);

        if (!$result || $result->RecordCount() == 0) {
            return false;
        }

        $code_data = $result->FetchRow();

        // Mark code as used
        $code_id = (int)$code_data['code_id'];
        self::$db->execute("UPDATE db_oauth_codes SET is_used = 1 WHERE code_id = $code_id");

        // Generate tokens
        $access_token = bin2hex(random_bytes(32));
        $refresh_token = bin2hex(random_bytes(32));

        $usr_id = (int)$code_data['usr_id'];
        $scopes = $code_data['scopes'];

        $sql = "INSERT INTO db_oauth_tokens
                (usr_id, client_id, access_token, refresh_token, token_type, scopes, expires_at, refresh_expires_at, created_at)
                VALUES ($usr_id, '$client_id_safe', '$access_token', '$refresh_token', 'Bearer', '$scopes',
                        DATE_ADD(NOW(), INTERVAL 1 HOUR),
                        DATE_ADD(NOW(), INTERVAL 30 DAY),
                        NOW())";

        if (self::$db->execute($sql)) {
            return [
                'access_token' => $access_token,
                'refresh_token' => $refresh_token,
                'token_type' => 'Bearer',
                'expires_in' => 3600,
                'scope' => implode(' ', json_decode($scopes, true))
            ];
        }

        return false;
    }

    /**
     * Refresh access token
     * @param string $refresh_token Refresh token
     * @param string $client_id Client ID
     * @return array|false New token data or false
     */
    public static function refreshToken($refresh_token, $client_id) {
        self::init();

        $refresh_safe = VDatabase::escape($refresh_token);
        $client_id_safe = VDatabase::escape($client_id);

        // Validate refresh token
        $sql = "SELECT * FROM db_oauth_tokens
                WHERE refresh_token = '$refresh_safe'
                AND client_id = '$client_id_safe'
                AND refresh_expires_at > NOW()
                AND is_revoked = 0";

        $result = self::$db->execute($sql);

        if (!$result || $result->RecordCount() == 0) {
            return false;
        }

        $token_data = $result->FetchRow();

        // Revoke old token
        $token_id = (int)$token_data['token_id'];
        self::$db->execute("UPDATE db_oauth_tokens SET is_revoked = 1 WHERE token_id = $token_id");

        // Generate new tokens
        $new_access_token = bin2hex(random_bytes(32));
        $new_refresh_token = bin2hex(random_bytes(32));

        $usr_id = (int)$token_data['usr_id'];
        $scopes = $token_data['scopes'];

        $sql = "INSERT INTO db_oauth_tokens
                (usr_id, client_id, access_token, refresh_token, token_type, scopes, expires_at, refresh_expires_at, created_at)
                VALUES ($usr_id, '$client_id_safe', '$new_access_token', '$new_refresh_token', 'Bearer', '$scopes',
                        DATE_ADD(NOW(), INTERVAL 1 HOUR),
                        DATE_ADD(NOW(), INTERVAL 30 DAY),
                        NOW())";

        if (self::$db->execute($sql)) {
            return [
                'access_token' => $new_access_token,
                'refresh_token' => $new_refresh_token,
                'token_type' => 'Bearer',
                'expires_in' => 3600
            ];
        }

        return false;
    }

    /**
     * Validate access token
     * @param string $access_token Access token
     * @return array|false User and scope data or false
     */
    public static function validateToken($access_token) {
        self::init();

        $token_safe = VDatabase::escape($access_token);

        $sql = "SELECT t.*, u.usr_user, u.usr_email
                FROM db_oauth_tokens t
                JOIN db_accountuser u ON t.usr_id = u.usr_id
                WHERE t.access_token = '$token_safe'
                AND t.expires_at > NOW()
                AND t.is_revoked = 0";

        $result = self::$db->execute($sql);

        if ($result && $result->RecordCount() > 0) {
            $data = $result->FetchRow();
            return [
                'usr_id' => $data['usr_id'],
                'username' => $data['usr_user'],
                'email' => $data['usr_email'],
                'scopes' => json_decode($data['scopes'], true)
            ];
        }

        return false;
    }

    /**
     * Revoke token
     * @param string $access_token Access token
     * @return bool Success
     */
    public static function revokeToken($access_token) {
        self::init();

        $token_safe = VDatabase::escape($access_token);

        $sql = "UPDATE db_oauth_tokens SET is_revoked = 1 WHERE access_token = '$token_safe'";
        return self::$db->execute($sql);
    }
}
