<?php

namespace App\Services\Firebase;

use App\Exceptions\CustomWebException;
use App\Models\NotificationLog;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Factory;
use Kreait\Firebase\Messaging\CloudMessage;

class FirebaseNotificationService
{
    private const MAX_BATCH_SIZE = 500; // Firebase limit is 500 per batch

    private ?Factory $factory = null;

    private static ?self $instance = null;

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

    /**
     * Get singleton instance
     */
    public static function getInstance(): self
    {
        if (! self::$instance) {
            self::$instance = new self;
        }

        return self::$instance;
    }

    /**
     * Send notification to a single device
     *
     * @param  string  $title  Notification title
     * @param  string  $message  Notification body
     * @param  string  $deviceToken  FCM token
     * @param  array  $additionalData  Optional additional data payload
     * @param  string|null  $imageUrl  Optional image URL for rich notifications
     *
     * @throws \Exception
     */
    public function sendNotification(
        string $title,
        string $message,
        string $deviceToken,
        array $additionalData = [],
        ?string $imageUrl = null
    ): bool {
        try {
            $notification = [
                'title' => $title,
                'body' => $message,
            ];

            if ($imageUrl) {
                $notification['image'] = $imageUrl;
            }

            $data = array_merge([
                'id' => (string) time(),
                'type' => 'default',
            ], $additionalData);

            $message = CloudMessage::new()
                ->withNotification($notification)
                ->toToken($deviceToken)
                ->withData($data);

            Log::info('Sending FCM notification:', [
                'token' => $deviceToken,
                'notification' => $notification,
                'data' => $data,
            ]);

            if (! $this->factory) {
                return false;
            }

            $messaging = $this->factory->createMessaging();

            $response = $messaging->send($message);

            $this->handleResponse($response, $deviceToken);
            $this->logNotification($notification, $data, 'success');

            return true;

        } catch (MessagingException $e) {
            $this->handleMessagingException($e, $deviceToken);

            return false;

        } catch (\Exception $e) {
            Log::error('Firebase notification error:', [
                'error' => $e->getMessage(),
                'token' => $deviceToken,
            ]);

            $this->logNotification([
                'error' => $e->getMessage(),
                'token' => $deviceToken,
            ], [], 'failed');

            throw $e;
        }
    }

    /**
     * Send notification to multiple devices
     *
     * @param  array|Collection  $deviceTokens
     * @return array Results for each token
     */
    public function sendBatchNotifications(
        string $title,
        string $message,
        $deviceTokens,
        array $additionalData = [],
        ?string $imageUrl = null
    ): array {
        if ($deviceTokens instanceof Collection) {
            $deviceTokens = $deviceTokens->toArray();
        }

        $results = [];
        $chunks = array_chunk($deviceTokens, self::MAX_BATCH_SIZE);

        foreach ($chunks as $tokenChunk) {
            foreach ($tokenChunk as $token) {
                try {
                    $success = $this->sendNotification(
                        $title,
                        $message,
                        $token,
                        $additionalData,
                        $imageUrl
                    );
                    $results[$token] = $success;
                } catch (\Exception $e) {
                    Log::error('Batch notification error:', [
                        'token' => $token,
                        'error' => $e->getMessage(),
                    ]);
                    $results[$token] = false;
                }
            }
        }

        return $results;
    }

    /**
     * Send notification to all users or filtered subset
     *
     * @param  array  $filters  Query filters for users
     */
    public function sendNotificationToUsers(
        string $title,
        string $message,
        mixed $modal,
        array $filters = [],
        array $additionalData = [],
        ?string $imageUrl = null
    ): array {
        $query = $modal::whereNotNull('fcm_token');

        foreach ($filters as $column => $value) {
            $query->where($column, $value);
        }

        $tokens = $query->pluck('fcm_token');

        return $this->sendBatchNotifications(
            $title,
            $message,
            $tokens,
            $additionalData,
            $imageUrl
        );
    }

    /**
     * Initialize Firebase
     *
     * @throws \Exception
     */
    private function setup(): void
    {
        try {
            $admin_sdk = getJsonFile('admin-sdk.json', 'firebase');

            if (! $admin_sdk) {
                return;
            }

            $this->factory = (new Factory)
                ->withServiceAccount([
                    'client_email' => data_get($admin_sdk, 'client_email'),
                    'private_key' => data_get($admin_sdk, 'private_key'),
                    'project_id' => data_get($admin_sdk, 'project_id'),
                    'type' => 'service_account',
                ]);
        } catch (\Throwable $th) {
            Log::error('Firebase setup error:', ['error' => $th->getMessage()]);

            throw new CustomWebException('Firebase setup error: '.$th->getMessage());
        }
    }

    /**
     * Handle FCM response and invalid tokens
     */
    private function handleResponse(array $response, string $token): void
    {
        if (isset($response['name'])) {
            Log::info('FCM notification sent successfully', [
                'messageId' => $response['name'],
                'token' => $token,
            ]);
        }
    }

    /**
     * Handle messaging exceptions and invalid tokens
     */
    private function handleMessagingException(MessagingException $e, string $token): void
    {
        Log::error('FCM Messaging Exception:', [
            'error' => $e->getMessage(),
            'token' => $token,
        ]);

        // Handle invalid token errors
        if (str_contains($e->getMessage(), 'invalid-registration-token') ||
            str_contains($e->getMessage(), 'registration-token-not-registered')) {

            $this->logNotification([
                'error' => $e->getMessage(),
                'token' => $token,
            ], [], 'failed');
        }

        $this->logNotification([
            'error' => $e->getMessage(),
            'token' => $token,
        ], [], 'failed');
    }

    /**
     * Log notification attempt
     */
    private function logNotification(array $notification, array $data, string $status): void
    {
        NotificationLog::create([
            'data' => json_encode([
                'notification' => $notification,
                'data' => $data,
            ]),
            'status' => $status,
        ]);
    }

    /**
     * Prevent cloning of singleton
     */
    private function __clone() {}
}
