<?php

namespace App\Libraries;

use App\Models\DeviceTokenModel;

/**
 * Firebase Cloud Messaging (FCM) Notification Service
 * 
 * This service uses the Firebase Cloud Messaging HTTP v1 API with OAuth 2.0 authentication.
 * Messages are sent individually using the messages:send endpoint (HTTP v1 API doesn't support
 * a batch endpoint), but are processed efficiently in batches with proper error handling.
 * 
 * Features:
 * - OAuth 2.0 authentication using service account credentials
 * - Efficient batch processing of multiple tokens
 * - Automatic token deactivation for invalid tokens
 * - Support for Android and iOS platforms
 * - Detailed error reporting and logging
 */
class FcmNotificationService
{
    private $projectId;
    private $serviceAccountPath;
    private $deviceTokenModel;
    private $accessToken;
    private $tokenExpiry;

    public function __construct()
    {
        // Get FCM configuration from environment
        $this->projectId = getenv('FCM_PROJECT_ID');
        $serviceAccountPath = getenv('FCM_SERVICE_ACCOUNT_PATH');
        
        if (!$this->projectId || !$serviceAccountPath) {
            throw new \Exception('FCM_PROJECT_ID and FCM_SERVICE_ACCOUNT_PATH must be configured in environment');
        }
        
        // Convert relative path to absolute path
        if (strpos($serviceAccountPath, '/') !== 0 && strpos($serviceAccountPath, ':') === false) {
            // It's a relative path, make it absolute from project root
            $this->serviceAccountPath = ROOTPATH . $serviceAccountPath;
        } else {
            // It's already an absolute path
            $this->serviceAccountPath = $serviceAccountPath;
        }
        
        if (!file_exists($this->serviceAccountPath)) {
            throw new \Exception('FCM service account file not found at: ' . $this->serviceAccountPath);
        }
        
        $this->deviceTokenModel = new DeviceTokenModel();
    }
    
    /**
     * Get OAuth 2.0 access token for Firebase HTTP v1 API
     * 
     * @return string Access token
     */
    private function getAccessToken()
    {
        // Return cached token if still valid
        if ($this->accessToken && $this->tokenExpiry && time() < $this->tokenExpiry - 300) {
            return $this->accessToken;
        }
        
        try {
            // Read service account JSON
            log_message('info', 'Reading service account from: ' . $this->serviceAccountPath);
            $serviceAccountJson = file_get_contents($this->serviceAccountPath);
            
            if ($serviceAccountJson === false) {
                throw new \Exception('Failed to read service account file');
            }
            
            $serviceAccount = json_decode($serviceAccountJson, true);
            
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \Exception('Invalid JSON in service account file: ' . json_last_error_msg());
            }
            
            if (!isset($serviceAccount['private_key']) || !isset($serviceAccount['client_email'])) {
                throw new \Exception('Invalid service account JSON file - missing private_key or client_email');
            }
            
            log_message('info', 'Service account loaded for: ' . $serviceAccount['client_email']);
            
            // Create JWT
            $now = time();
            $expiry = $now + 3600; // Token valid for 1 hour
            
            $header = [
                'alg' => 'RS256',
                'typ' => 'JWT'
            ];
            
            $payload = [
                'iss' => $serviceAccount['client_email'],
                'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
                'aud' => 'https://oauth2.googleapis.com/token',
                'iat' => $now,
                'exp' => $expiry
            ];
            
            $base64UrlHeader = $this->base64UrlEncode(json_encode($header));
            $base64UrlPayload = $this->base64UrlEncode(json_encode($payload));
            $signatureInput = $base64UrlHeader . '.' . $base64UrlPayload;
            
            // Sign with private key
            $privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
            if (!$privateKey) {
                throw new \Exception('Failed to load private key from service account');
            }
            
            openssl_sign($signatureInput, $signature, $privateKey, OPENSSL_ALGO_SHA256);
            openssl_free_key($privateKey);
            
            $base64UrlSignature = $this->base64UrlEncode($signature);
            $jwt = $signatureInput . '.' . $base64UrlSignature;
            
            // Exchange JWT for access token
            $ch = curl_init('https://oauth2.googleapis.com/token');
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $jwt
            ]));
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
            
            // SSL verification - disable in development, enable in production
            // For local development (WAMP/XAMPP), you may need to disable SSL verification
            // In production, ensure proper SSL certificates are configured
            if (ENVIRONMENT === 'development') {
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            } else {
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
            }
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $curlError = curl_error($ch);
            curl_close($ch);
            
            if ($curlError) {
                log_message('error', 'cURL Error: ' . $curlError);
                throw new \Exception('Failed to get access token: cURL error - ' . $curlError);
            }
            
            if ($httpCode !== 200) {
                log_message('error', 'OAuth Response (HTTP ' . $httpCode . '): ' . $response);
                throw new \Exception('Failed to get access token (HTTP ' . $httpCode . '): ' . $response);
            }
            
            $tokenData = json_decode($response, true);
            
            if (!isset($tokenData['access_token'])) {
                throw new \Exception('No access token in response');
            }
            
            // Cache the token
            $this->accessToken = $tokenData['access_token'];
            $this->tokenExpiry = $now + ($tokenData['expires_in'] ?? 3600);
            
            return $this->accessToken;
            
        } catch (\Exception $e) {
            log_message('error', 'FCM Access Token Error: ' . $e->getMessage());
            throw $e;
        }
    }
    
    /**
     * Base64 URL encode
     */
    private function base64UrlEncode($data)
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    /**
     * Build a message payload for FCM HTTP v1 API
     * 
     * @param string $token FCM token
     * @param string $title Notification title
     * @param string $message Notification message
     * @param array $data Additional data to send with notification
     * @return array Message payload structure
     */
    private function buildMessagePayload($token, $title, $message, $data = [])
    {
        return [
            'message' => [
                'token' => $token,
                'notification' => [
                    'title' => $title,
                    'body' => $message
                ],
                'data' => array_merge($data, [
                    'title' => $title,
                    'message' => $message,
                    'click_action' => 'FLUTTER_NOTIFICATION_CLICK'
                ]),
                'android' => [
                    'priority' => 'high',
                    'notification' => [
                        'sound' => 'default',
                        'click_action' => 'FLUTTER_NOTIFICATION_CLICK',
                        'default_sound' => true,
                        'default_vibrate_timings' => true,
                        'channel_id' => 'fcm_default_channel'
                    ]
                ],
                'apns' => [
                    'payload' => [
                        'aps' => [
                            'sound' => 'default',
                            'badge' => 1
                        ]
                    ]
                ]
            ]
        ];
    }

    /**
     * Send notification to specific user(s)
     * 
     * @param string|array $msisdn Single phone number or array of phone numbers
     * @param string $title Notification title
     * @param string $message Notification message
     * @param array $data Additional data to send with notification
     * @return array Response with success/failure count
     */
    public function sendToUser($msisdn, $title, $message, $data = [])
    {
        $msisdns = is_array($msisdn) ? $msisdn : [$msisdn];
        $tokens = [];
        
        // Get all tokens for the specified users
        foreach ($msisdns as $number) {
            $userTokens = $this->deviceTokenModel->getUserTokens($number);
            foreach ($userTokens as $tokenData) {
                $tokens[] = $tokenData['fcm_token'];
            }
        }
        
        if (empty($tokens)) {
            return [
                'success' => false,
                'message' => 'No active device tokens found for the specified user(s)',
                'sent' => 0,
                'failed' => 0
            ];
        }
        
        return $this->sendToTokens($tokens, $title, $message, $data);
    }

    /**
     * Send notification to all registered users
     * 
     * @param string $title Notification title
     * @param string $message Notification message
     * @param array $data Additional data to send with notification
     * @return array Response with success/failure count
     */
    public function sendToAll($title, $message, $data = [])
    {
        $allTokens = $this->deviceTokenModel->getAllActiveTokens();
        
        if (empty($allTokens)) {
            return [
                'success' => false,
                'message' => 'No active device tokens found',
                'sent' => 0,
                'failed' => 0
            ];
        }
        
        $tokens = array_column($allTokens, 'fcm_token');
        return $this->sendToTokens($tokens, $title, $message, $data);
    }

    /**
     * Send notification to specific device tokens
     * 
     * @param array $tokens Array of FCM tokens
     * @param string $title Notification title
     * @param string $message Notification message
     * @param array $data Additional data to send with notification
     * @return array Response with success/failure count
     */
    public function sendToTokens($tokens, $title, $message, $data = [])
    {
        // Process tokens in manageable batches to avoid overwhelming the API
        // Each batch processes messages sequentially for reliability
        $batchSize = 100; // Process 100 at a time to balance performance and reliability
        $batches = array_chunk($tokens, $batchSize);
        
        $totalSent = 0;
        $totalFailed = 0;
        $responses = [];
        
        foreach ($batches as $batch) {
            $result = $this->sendBatch($batch, $title, $message, $data);
            $totalSent += $result['sent'];
            $totalFailed += $result['failed'];
            $responses[] = $result;
        }
        
        return [
            'success' => $totalSent > 0,
            'message' => "Sent to {$totalSent} devices, {$totalFailed} failed",
            'sent' => $totalSent,
            'failed' => $totalFailed,
            'details' => $responses
        ];
    }

    /**
     * Send notification batch to FCM using HTTP v1 API
     * 
     * Note: Firebase HTTP v1 API does not have a batch endpoint.
     * Messages are sent individually using the messages:send endpoint.
     * This method processes multiple tokens efficiently by sending them sequentially
     * with proper error handling for each message.
     * 
     * @param array $tokens Array of FCM tokens
     * @param string $title Notification title
     * @param string $message Notification message
     * @param array $data Additional data to send with notification
     * @return array Response with success/failure count and detailed results
     */
    private function sendBatch($tokens, $title, $message, $data = [])
    {
        if (empty($tokens)) {
            return [
                'success' => false,
                'sent' => 0,
                'failed' => 0,
                'errors' => []
            ];
        }

        $accessToken = $this->getAccessToken();
        $successCount = 0;
        $failureCount = 0;
        $errors = [];
        
        // Send messages individually (HTTP v1 API doesn't support batch endpoint)
        foreach ($tokens as $index => $token) {
            try {
                $result = $this->sendToSingleToken($token, $title, $message, $data, $accessToken);
                if ($result['success']) {
                    $successCount++;
                } else {
                    $failureCount++;
                    $errorCode = $result['error']['code'] ?? 'UNKNOWN';
                    $errorMessage = $result['error']['message'] ?? $result['error'] ?? 'Unknown error';
                    
                    $errors[] = [
                        'index' => $index,
                        'token' => substr($token, 0, 20) . '...',
                        'code' => $errorCode,
                        'message' => $errorMessage
                    ];
                    
                    log_message('warning', "FCM message {$index} failed: {$errorCode} - {$errorMessage}");
                }
            } catch (\Exception $e) {
                $failureCount++;
                $errors[] = [
                    'index' => $index,
                    'token' => substr($token, 0, 20) . '...',
                    'code' => 'EXCEPTION',
                    'message' => $e->getMessage()
                ];
                log_message('error', "FCM send error for token at index {$index}: " . $e->getMessage());
            }
        }
        
        return [
            'success' => $successCount > 0,
            'sent' => $successCount,
            'failed' => $failureCount,
            'errors' => $errors,
            'total' => count($tokens)
        ];
    }
    
    /**
     * Send notification to a single token using HTTP v1 API
     * 
     * This method is kept for backwards compatibility, but batch sending is preferred
     * for multiple tokens. For single token sends, this uses the standard messages:send endpoint.
     * 
     * @param string $token FCM token
     * @param string $title Notification title
     * @param string $message Notification message
     * @param array $data Additional data
     * @param string $accessToken OAuth access token
     * @return array Result
     */
    private function sendToSingleToken($token, $title, $message, $data, $accessToken)
    {
        $url = "https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send";
        
        // Use the helper method to build the payload
        $payload = $this->buildMessagePayload($token, $title, $message, $data);
        
        $headers = [
            'Authorization: Bearer ' . $accessToken,
            'Content-Type: application/json'
        ];
        
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        // SSL verification - disable in development, enable in production
        if (ENVIRONMENT === 'development') {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        }
        
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        
        $result = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        if (curl_errno($ch)) {
            $error = curl_error($ch);
            curl_close($ch);
            log_message('error', 'FCM cURL Error: ' . $error);
            return ['success' => false, 'error' => $error];
        }
        
        curl_close($ch);
        
        $response = json_decode($result, true);
        
        // Handle errors
        if ($httpCode !== 200) {
            $errorCode = $response['error']['code'] ?? 'UNKNOWN';
            $errorMessage = $response['error']['message'] ?? $result;
            
            log_message('error', "FCM HTTP v1 Error: {$httpCode} - {$errorMessage}");
            
            // Deactivate invalid tokens
            $invalidErrors = ['NOT_FOUND', 'UNREGISTERED', 'INVALID_ARGUMENT'];
            if (in_array($errorCode, $invalidErrors) || 
                strpos($errorMessage, 'not a valid FCM registration token') !== false) {
                $this->deviceTokenModel->deactivateToken($token);
                log_message('info', 'Deactivated invalid FCM token: ' . $errorCode);
            }
            
            return [
                'success' => false,
                'http_code' => $httpCode,
                'error' => [
                    'code' => $errorCode,
                    'message' => $errorMessage
                ]
            ];
        }
        
        return ['success' => true, 'response' => $response];
    }

    /**
     * Send notification for a new tip
     * 
     * @param array $tip Tip data
     * @return array Response
     */
    public function sendNewTipNotification($tip)
    {
        $title = $tip['title'] ?? 'New Tip Available';
        $category = $tip['category'] ?? 'Tip';
        $message = "New {$category} is available";
        
        $data = [
            'type' => 'new_tip',
            'tip_id' => $tip['id'] ?? null,
            'category' => $category,
            'tag' => $tip['tag'] ?? null
        ];
        
        return $this->sendToAll($title, $message, $data);
    }
}

