<?php

namespace App\Services;

use App\Models\UserDevice;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class FcmService
{
    /**
     * Send new SMS FCM Push Notification to target device(s).
     * (Small and single message push)
     *
     * @param int $userId
     * @param string $recipient
     * @param string $messageBody
     * @param int|string|null $smsId
     * @param int|string|null $targetDeviceId
     * @return array{sent: int, failed: int}
     */
    public static function sendNewSmsNotification(int $userId, string $recipient, string $messageBody, $smsId = null, $targetDeviceId = null): array
    {
        $dataPayload = [
            'type' => 'NEW_SMS_JOB',
            'event' => 'single_message_submitted',
            'sms_id' => (string) ($smsId ?? date('YmdHis')),
            'recipient' => (string) $recipient,
            'phone' => (string) $recipient,
            'message' => (string) $messageBody,
            'body' => (string) $messageBody,
            'created_at' => now()->toIso8601String(),
        ];

        return self::sendPushToUser(
            $userId,
            $dataPayload,
            'New SMS Request',
            "Recipient: {$recipient}",
            $targetDeviceId,
            $smsId ? [(int) $smsId] : []
        );
    }

    /**
     * Send Group Message Submitted push notification to target device(s).
     *
     * @param int $userId
     * @param int|string $groupId
     * @param string $groupName
     * @param int $totalCount
     * @param string|null $messageBody
     * @param int|string|null $targetDeviceId
     * @param array<int> $messageIds
     * @return array{sent: int, failed: int}
     */
    public static function sendGroupMessageNotification(int $userId, $groupId, string $groupName, int $totalCount, ?string $messageBody = null, $targetDeviceId = null, array $messageIds = []): array
    {
        $dataPayload = [
            'type' => 'group_message_submitted',
            'event' => 'group_message_submitted',
            'group_id' => (string) $groupId,
            'group_name' => (string) $groupName,
            'total_count' => (string) $totalCount,
            'message' => (string) ($messageBody ?? ''),
            'body' => (string) ($messageBody ?? ''),
            'created_at' => now()->toIso8601String(),
        ];

        return self::sendPushToUser(
            $userId,
            $dataPayload,
            'Group Message Submitted',
            "Group '{$groupName}' submitted with {$totalCount} messages.",
            $targetDeviceId,
            $messageIds
        );
    }

    /**
     * Send Large Message Submitted push notification to target device(s).
     *
     * @param int $userId
     * @param int $totalCount
     * @param string|null $campaignTitle
     * @param string|null $messageBody
     * @param int|string|null $targetDeviceId
     * @param array<int> $messageIds
     * @return array{sent: int, failed: int}
     */
    public static function sendLargeMessageNotification(int $userId, int $totalCount, ?string $campaignTitle = null, ?string $messageBody = null, $targetDeviceId = null, array $messageIds = []): array
    {
        $title = $campaignTitle ?: 'Large Batch';
        $dataPayload = [
            'type' => 'large_message_submitted',
            'event' => 'large_message_submitted',
            'campaign_title' => (string) $title,
            'total_count' => (string) $totalCount,
            'message' => (string) ($messageBody ?? ''),
            'body' => (string) ($messageBody ?? ''),
            'created_at' => now()->toIso8601String(),
        ];

        return self::sendPushToUser(
            $userId,
            $dataPayload,
            'Large Message Submitted',
            "A batch of {$totalCount} messages has been submitted to the queue.",
            $targetDeviceId,
            $messageIds
        );
    }

    /**
     * Send arbitrary FCM Push Notification payload to target or all active user devices.
     *
     * @param int $userId
     * @param array<string, mixed> $dataPayload
     * @param string $title
     * @param string $body
     * @param int|string|null $targetDeviceId
     * @param array<int> $messageIds
     * @return array{sent: int, failed: int}
     */
    public static function sendPushToUser(int $userId, array $dataPayload, string $title, string $body, $targetDeviceId = null, array $messageIds = []): array
    {
        $query = UserDevice::where('user_id', $userId)
            ->where('status', 1)
            ->whereNotNull('fcm_token')
            ->where('fcm_token', '!=', '');

        if ($targetDeviceId) {
            $query->where(function ($q) use ($targetDeviceId) {
                $q->where('id', $targetDeviceId)
                  ->orWhere('device_id', $targetDeviceId);
            });
        }

        $devices = $query->get();

        if ($devices->isEmpty()) {
            Log::info("No registered FCM tokens found for user_id: {$userId}");
            return ['sent' => 0, 'failed' => 0];
        }

        $serviceAccountPath = self::resolveServiceAccountPath();

        $sentCount = 0;
        $failedCount = 0;

        // Ensure all values in dataPayload are strings (required by FCM data payload)
        $stringDataPayload = [];
        foreach ($dataPayload as $key => $val) {
            $stringDataPayload[(string) $key] = is_null($val) ? '' : (string) $val;
        }

        if (!isset($stringDataPayload['title']) || empty($stringDataPayload['title'])) {
            $stringDataPayload['title'] = (string) $title;
        }
        if (!isset($stringDataPayload['notification_title']) || empty($stringDataPayload['notification_title'])) {
            $stringDataPayload['notification_title'] = (string) $title;
        }
        if (!isset($stringDataPayload['notification_body']) || empty($stringDataPayload['notification_body'])) {
            $stringDataPayload['notification_body'] = (string) $body;
        }
        if (!isset($stringDataPayload['body']) || empty($stringDataPayload['body'])) {
            $stringDataPayload['body'] = (string) $body;
        }

        foreach ($devices as $device) {
            $fcmToken = $device->fcm_token;
            if (empty($fcmToken)) {
                continue;
            }

            // 1. When sending any message to target device by FCM, store device_id on the message(s)
            if (!empty($messageIds)) {
                \App\Models\SmsMessage::whereIn('id', $messageIds)
                    ->where(function ($q) {
                        $q->whereNull('device_id')->orWhere('device_id', 0);
                    })
                    ->update(['device_id' => $device->id]);
            } elseif (!empty($stringDataPayload['sms_id']) && is_numeric($stringDataPayload['sms_id'])) {
                \App\Models\SmsMessage::where('id', (int) $stringDataPayload['sms_id'])
                    ->where(function ($q) {
                        $q->whereNull('device_id')->orWhere('device_id', 0);
                    })
                    ->update(['device_id' => $device->id]);
            }

            try {
                if ($serviceAccountPath && file_exists($serviceAccountPath)) {
                    $jsonConfig = json_decode(file_get_contents($serviceAccountPath), true);
                    $projectId = $jsonConfig['project_id'] ?? null;
                    $accessToken = self::getGoogleAccessToken($jsonConfig);

                    if ($projectId && $accessToken) {
                        $url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send";
                        $response = Http::withHeaders([
                            'Authorization' => 'Bearer ' . $accessToken,
                            'Content-Type' => 'application/json',
                        ])->post($url, [
                            'message' => [
                                'token' => $fcmToken,
                                'data' => $stringDataPayload,
                                'android' => [
                                    'priority' => 'high',
                                    'direct_boot_ok' => true,
                                ],
                            ],
                        ]);

                        if ($response->successful()) {
                            $sentCount++;
                            Log::info("FCM v1 push notification ({$stringDataPayload['type']}) sent successfully to device {$device->device_id} (ID: {$device->id})");
                        } else {
                            $failedCount++;
                            Log::warning("FCM v1 push failed for device {$device->device_id}: " . $response->body());
                        }
                    }
                } else {
                    Log::info("FCM Notification simulated (No Server Key or JSON Service Account configured) for device {$device->device_id} (ID: {$device->id}): type={$stringDataPayload['type']}");
                    $sentCount++;
                }
            } catch (\Throwable $e) {
                $failedCount++;
                Log::error("FCM Exception for device {$device->device_id}: " . $e->getMessage());
            }
        }

        return ['sent' => $sentCount, 'failed' => $failedCount];
    }

    /**
     * Helper to obtain OAuth2 Access Token for Firebase HTTP v1 API.
     */
    private static function getGoogleAccessToken(array $jsonConfig): ?string
    {
        try {
            $clientEmail = $jsonConfig['client_email'] ?? null;
            $privateKey = $jsonConfig['private_key'] ?? null;

            if (!$clientEmail || !$privateKey) return null;

            $now = time();
            $header = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
            $payload = base64_encode(json_encode([
                'iss' => $clientEmail,
                'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
                'aud' => 'https://oauth2.googleapis.com/token',
                'exp' => $now + 3600,
                'iat' => $now,
            ]));

            $unsignedToken = "{$header}.{$payload}";
            openssl_sign($unsignedToken, $signature, $privateKey, 'SHA256');
            $jwt = "{$unsignedToken}." . base64_encode($signature);

            $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $jwt,
            ]);

            return $response->json('access_token');
        } catch (\Throwable $e) {
            Log::error("Failed to generate Google OAuth2 token: " . $e->getMessage());
            return null;
        }
    }

    private static function resolveServiceAccountPath(): ?string
    {
        $defaultPath = config('services.fcm.key_path');
        if (file_exists($defaultPath)) {
            return $defaultPath;
        }

        $globMatches = glob(storage_path('app/*firebase*.json'));
        if (!empty($globMatches)) {
            return $globMatches[0];
        }

        return null;
    }
}
