<?php
/**
 * VK API Client for posting to groups/walls
 * Compatible with PHP 7.2+
 *
 * Требования:
 * - VK Access Token с правами: wall, photos, groups
 * - Получить токен: https://vk.com/dev → Create App → Get Token
 */

class VKAPI
{
    private $accessToken;
    private $apiVersion = '5.199';
    private $baseUrl = 'https://api.vk.com/method/';
    private $userId;

    public function __construct($accessToken)
    {
        $this->accessToken = $accessToken;
    }

    /**
     * Make API request to VK
     *
     * @param string $method API method
     * @param array $params Parameters
     * @return array Response
     */
    private function request($method, $params = [])
    {
        $params['access_token'] = $this->accessToken;
        $params['v'] = $this->apiVersion;

        $url = $this->baseUrl . $method;

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($params),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);

        $response = curl_exec($ch);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new RuntimeException("VK API connection error: {$error}");
        }

        $data = json_decode($response, true);

        if (isset($data['error'])) {
            $errorMsg = isset($data['error']['error_msg']) ? $data['error']['error_msg'] : 'Unknown error';
            $errorCode = isset($data['error']['error_code']) ? $data['error']['error_code'] : 0;
            throw new RuntimeException("VK API error [{$errorCode}]: {$errorMsg}");
        }

        return isset($data['response']) ? $data['response'] : [];
    }

    /**
     * Get current user info
     *
     * @return array User info
     */
    public function getMe()
    {
        $result = $this->request('users.get', [
            'fields' => 'photo_100,screen_name'
        ]);

        if (!empty($result[0])) {
            $this->userId = $result[0]['id'];
            return $result[0];
        }

        return [];
    }

    /**
     * Get groups where user can post
     *
     * @param int $count Number of groups to return
     * @return array Groups list
     */
    public function getGroups($count = 100)
    {
        $result = $this->request('groups.get', [
            'extended' => 1,
            'filter' => 'admin,editor,moder',
            'fields' => 'name,screen_name,photo_100,can_post',
            'count' => $count
        ]);

        $groups = [];
        if (isset($result['items'])) {
            foreach ($result['items'] as $group) {
                // Only groups where posting is allowed
                if (!empty($group['can_post']) || isset($group['admin_level'])) {
                    $groups[] = [
                        'id' => '-' . $group['id'], // Negative for group wall
                        'name' => $group['name'],
                        'screen_name' => isset($group['screen_name']) ? $group['screen_name'] : '',
                        'photo' => isset($group['photo_100']) ? $group['photo_100'] : '',
                    ];
                }
            }
        }

        return $groups;
    }

    /**
     * Upload photo to VK from URL
     *
     * @param int $groupId Group ID (without minus, positive number)
     * @param string $photoUrl Photo URL to upload
     * @return string Attachment string (photo{owner}_{id})
     */
    public function uploadPhotoFromUrl($groupId, $photoUrl)
    {
        $groupId = abs((int)$groupId);

        // Get upload server
        try {
            $uploadServer = $this->request('photos.getWallUploadServer', [
                'group_id' => $groupId
            ]);
        } catch (Exception $e) {
            throw new RuntimeException('Не удалось получить сервер загрузки VK: ' . $e->getMessage());
        }

        if (!isset($uploadServer['upload_url'])) {
            throw new RuntimeException('VK не вернул URL для загрузки фото');
        }

        // Download photo from URL with context for HTTPS
        $context = stream_context_create([
            'http' => [
                'timeout' => 30,
                'user_agent' => 'VH-Posting-System/1.0'
            ],
            'ssl' => [
                'verify_peer' => true,
                'verify_peer_name' => true
            ]
        ]);

        $photoData = @file_get_contents($photoUrl, false, $context);
        if ($photoData === false) {
            throw new RuntimeException('Не удалось скачать фото с Flickr: ' . $photoUrl);
        }

        // Detect mime type
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->buffer($photoData) ?: 'image/jpeg';
        $extension = $mimeType === 'image/png' ? 'png' : 'jpg';

        // Save to temp file
        $tempFile = tempnam(sys_get_temp_dir(), 'vk_photo_');
        file_put_contents($tempFile, $photoData);

        // Upload photo using CURLFile
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $uploadServer['upload_url'],
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => [
                'photo' => new CURLFile($tempFile, $mimeType, 'photo.' . $extension)
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 120,
        ]);

        $uploadResponse = curl_exec($ch);
        $curlError = curl_error($ch);
        curl_close($ch);
        unlink($tempFile);

        if ($curlError) {
            throw new RuntimeException('Ошибка загрузки фото на VK: ' . $curlError);
        }

        $uploadData = json_decode($uploadResponse, true);
        if (!$uploadData) {
            throw new RuntimeException('Неверный ответ от сервера VK при загрузке');
        }

        if (empty($uploadData['photo']) || $uploadData['photo'] === '[]') {
            $errorMsg = isset($uploadData['error']) ? $uploadData['error'] : 'пустой ответ';
            throw new RuntimeException('VK не принял фото: ' . $errorMsg);
        }

        // Save photo
        try {
            $savedPhoto = $this->request('photos.saveWallPhoto', [
                'group_id' => $groupId,
                'photo' => $uploadData['photo'],
                'server' => $uploadData['server'],
                'hash' => $uploadData['hash']
            ]);
        } catch (Exception $e) {
            throw new RuntimeException('Не удалось сохранить фото в VK: ' . $e->getMessage());
        }

        if (empty($savedPhoto[0])) {
            throw new RuntimeException('VK не вернул данные сохранённого фото');
        }

        $photo = $savedPhoto[0];
        return 'photo' . $photo['owner_id'] . '_' . $photo['id'];
    }

    /**
     * Post to wall/group
     *
     * @param int $ownerId User ID or Group ID (negative for groups)
     * @param string $message Post text
     * @param array $attachments Array of attachments
     * @param bool $fromGroup Post from group name (only for groups)
     * @return array Post info
     */
    public function wallPost($ownerId, $message = '', $attachments = [], $fromGroup = true)
    {
        $params = [
            'owner_id' => $ownerId,
            'message' => $message,
        ];

        if (!empty($attachments)) {
            $params['attachments'] = implode(',', $attachments);
            $params['primary_attachments_mode'] = 'grid';
        }

        // If posting to group, post from group name
        if ($ownerId < 0 && $fromGroup) {
            $params['from_group'] = 1;
        }

        return $this->request('wall.post', $params);
    }

    /**
     * Post photos and text to a group
     *
     * @param int $groupId Group ID (will be converted to negative)
     * @param array $photoUrls Array of photo URLs
     * @param string $message Post text
     * @return array Result
     */
    public function post($groupId, $photoUrls = [], $message = '')
    {
        $attachments = [];
        $uploadErrors = [];
        $permissionError = false;

        // Make sure group ID is numeric
        $numericGroupId = (int)$groupId;
        if ($numericGroupId > 0) {
            $numericGroupId = -$numericGroupId;
        }

        // Try to upload each photo
        foreach ($photoUrls as $url) {
            try {
                $attachments[] = $this->uploadPhotoFromUrl(abs($numericGroupId), $url);
            } catch (Exception $e) {
                $errorMsg = $e->getMessage();
                $uploadErrors[] = $errorMsg;
                error_log("VK photo upload failed: " . $errorMsg);

                // Check if it's a permission error (error 15 = Access denied, error 27 = group auth)
                if (strpos($errorMsg, 'error [15]') !== false ||
                    strpos($errorMsg, 'error [27]') !== false ||
                    strpos($errorMsg, 'Access denied') !== false ||
                    strpos($errorMsg, 'group auth') !== false) {
                    $permissionError = true;
                    break; // Don't try other photos if it's a permission issue
                }
            }
        }

        // If permission error, try to post with photo links in text
        if ($permissionError && !empty($photoUrls)) {
            $photoLinks = "\n\n📷 Фото:\n" . implode("\n", $photoUrls);
            $messageWithPhotos = $message . $photoLinks;

            try {
                $result = $this->wallPost($numericGroupId, $messageWithPhotos, []);
                $result['warning'] = 'Фото добавлены как ссылки. Метод photos.getWallUploadServer не работает с токеном сообщества (это ограничение VK API: error 27 «group auth» или error 15 «access denied»). Чтобы фото прикреплялись как вложения, нужен ПОЛЬЗОВАТЕЛЬСКИЙ access token с правами wall, photos, groups, offline.';
                return $result;
            } catch (Exception $e) {
                throw new RuntimeException('Ошибка постинга: ' . $e->getMessage() . '. Также нет прав на загрузку фото.');
            }
        }

        // If all photos failed to upload for non-permission reasons, report the first error
        if (!empty($photoUrls) && empty($attachments) && !empty($uploadErrors)) {
            throw new RuntimeException('Ошибка загрузки фото: ' . $uploadErrors[0]);
        }

        return $this->wallPost($numericGroupId, $message, $attachments);
    }

    /**
     * Post to multiple groups at once
     *
     * @param array $groupIds Array of group IDs
     * @param array $photoUrls Array of photo URLs
     * @param string $message Post text
     * @return array Results for each group
     */
    public function postToMultiple($groupIds, $photoUrls = [], $message = '')
    {
        $results = [];

        foreach ($groupIds as $groupId) {
            try {
                $results[$groupId] = [
                    'success' => true,
                    'result' => $this->post($groupId, $photoUrls, $message),
                ];
            } catch (Exception $e) {
                $results[$groupId] = [
                    'success' => false,
                    'error' => $e->getMessage(),
                ];
            }

            // VK rate limit: max 3 requests per second
            usleep(350000);
        }

        return $results;
    }

    /**
     * Validate access token (supports both user and community tokens)
     *
     * @return array Validation result
     */
    public function validateToken()
    {
        // First try user token validation
        try {
            $user = $this->getMe();
            if (!empty($user)) {
                return [
                    'valid' => true,
                    'type' => 'user',
                    'user_id' => $user['id'],
                    'user_name' => trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')),
                    'screen_name' => $user['screen_name'] ?? '',
                ];
            }
        } catch (Exception $e) {
            // User token failed, try community token
        }

        // Try community token validation using groups.getById with group_id from token
        try {
            // For community tokens, we can get group info using groups.getById
            // The token should have access to its own group
            $result = $this->request('groups.getById', [
                'fields' => 'name,screen_name,photo_100'
            ]);

            if (!empty($result['groups'][0])) {
                $group = $result['groups'][0];
                return [
                    'valid' => true,
                    'type' => 'community',
                    'user_id' => '-' . $group['id'],
                    'user_name' => $group['name'] ?? 'Сообщество',
                    'screen_name' => $group['screen_name'] ?? '',
                ];
            }
            // VK API v5.199+ returns in different format
            if (!empty($result[0])) {
                $group = $result[0];
                return [
                    'valid' => true,
                    'type' => 'community',
                    'user_id' => '-' . $group['id'],
                    'user_name' => $group['name'] ?? 'Сообщество',
                    'screen_name' => $group['screen_name'] ?? '',
                ];
            }
        } catch (Exception $e) {
            return [
                'valid' => false,
                'error' => $e->getMessage(),
            ];
        }

        return ['valid' => false, 'error' => 'Invalid token'];
    }
}
