<?php

namespace App\Services\Providers;

use App\Exceptions\InvalidPushTokenException;
use App\Models\DeviceToken;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class FcmPushProvider
{
    public function send(DeviceToken $device, string $title, string $body, array $data = []): ?string
    {
        $config = config('clinic.fcm');
        if (! ($config['enabled'] ?? false)) {
            throw new RuntimeException('FCM push provider is disabled.');
        }

        $credentials = $this->credentials($config);
        $projectId = $config['project_id'] ?: ($credentials['project_id'] ?? null);
        if (! $projectId) {
            throw new RuntimeException('FCM project ID is not configured.');
        }

        $accessToken = $this->accessToken($credentials, (string) $projectId);
        $response = Http::withToken($accessToken)
            ->acceptJson()
            ->timeout(20)
            ->post("https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send", [
                'message' => [
                    'token' => $device->token,
                    'notification' => ['title' => $title, 'body' => $body],
                    'data' => collect($data)->mapWithKeys(fn ($value, $key) => [(string) $key => is_scalar($value) ? (string) $value : json_encode($value)])->all(),
                ],
            ]);

        if (! $response->successful()) {
            $payload = $response->json() ?: [];
            $text = json_encode($payload);
            if ($response->status() === 404 || str_contains((string) $text, 'UNREGISTERED')) {
                throw new InvalidPushTokenException('FCM token is unregistered or invalid.');
            }
            throw new RuntimeException('FCM request failed: '.($payload['error']['message'] ?? $response->body()));
        }

        return $response->json('name');
    }

    private function credentials(array $config): array
    {
        $raw = $config['service_account_json'] ?? null;
        $path = $config['service_account_path'] ?? null;

        if ($raw) {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }

        if ($path && is_file($path)) {
            $decoded = json_decode((string) file_get_contents($path), true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }

        throw new RuntimeException('FCM service-account credentials are not configured.');
    }

    private function accessToken(array $credentials, string $projectId): string
    {
        return Cache::remember("smart_clinic:fcm_access_token:{$projectId}", now()->addMinutes(50), function () use ($credentials) {
            foreach (['client_email', 'private_key', 'token_uri'] as $key) {
                if (empty($credentials[$key])) {
                    throw new RuntimeException("FCM service-account field {$key} is missing.");
                }
            }

            $now = time();
            $jwt = JWT::encode([
                'iss' => $credentials['client_email'],
                'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
                'aud' => $credentials['token_uri'],
                'iat' => $now,
                'exp' => $now + 3600,
            ], $credentials['private_key'], 'RS256');

            $response = Http::asForm()->timeout(20)->post($credentials['token_uri'], [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $jwt,
            ])->throw()->json();

            if (empty($response['access_token'])) {
                throw new RuntimeException('FCM OAuth token response did not contain an access token.');
            }

            return $response['access_token'];
        });
    }
}
