<?php

namespace Modules\PortalApi\Services\Push;

use Illuminate\Support\Facades\Log;
use Modules\PortalApi\Entities\PortalPushSubscription;

/**
 * Sends Web Push notifications via VAPID.
 *
 * Wraps minishlink/web-push when it's available; falls back to a no-op
 * + log message when it isn't, so the rest of the codebase can call
 * send() unconditionally during onboarding.
 *
 * To activate real sending:
 *   composer require minishlink/web-push
 *
 * Then set in .env:
 *   VAPID_PUBLIC_KEY="<base64url-encoded uncompressed P-256 public key>"
 *   VAPID_PRIVATE_KEY="<base64url-encoded P-256 private key>"
 *   VAPID_SUBJECT="mailto:admin@yourclinic.com"
 *
 * Generate keys with `php artisan portal:push:keys` (defined below).
 */
class PortalPushSender
{
    public function isReady(): bool
    {
        return class_exists(\Minishlink\WebPush\WebPush::class)
            && config('portal_push.public_key')
            && config('portal_push.private_key');
    }

    /**
     * Deliver one payload to one subscription. Returns true on accepted
     * (browser will receive it), false on permanent failure (subscription
     * is dead → caller should delete the row).
     *
     * Payload shape:
     *   ['title' => '…', 'body' => '…', 'url' => '/app/home', 'icon' => '…']
     * The PWA's service worker shows it.
     */
    public function send(PortalPushSubscription $sub, array $payload): bool
    {
        if (! $this->isReady()) {
            Log::info('[PortalPush] (no-op — library not installed)', [
                'owner_id' => $sub->owner_id,
                'endpoint' => $sub->endpoint,
                'payload'  => $payload,
            ]);
            return true;
        }

        $webPush = new \Minishlink\WebPush\WebPush([
            'VAPID' => [
                'subject'    => config('portal_push.subject'),
                'publicKey'  => config('portal_push.public_key'),
                'privateKey' => config('portal_push.private_key'),
            ],
        ]);

        $subscription = \Minishlink\WebPush\Subscription::create($sub->toWebPushArray());

        $report = $webPush->sendOneNotification(
            $subscription,
            json_encode($payload, JSON_UNESCAPED_SLASHES),
        );

        if ($report->isSuccess()) {
            $sub->forceFill([
                'last_used_at'          => now(),
                'consecutive_failures'  => 0,
            ])->save();
            return true;
        }

        $statusCode = $report->getResponse()?->getStatusCode();
        // 404/410 = the subscription is permanently gone (uninstalled the
        // PWA, revoked permission, etc) — caller should delete the row.
        if (in_array($statusCode, [404, 410], true)) {
            $sub->delete();
            return false;
        }

        $sub->forceFill([
            'last_failed_at'        => now(),
            'consecutive_failures'  => $sub->consecutive_failures + 1,
        ])->save();

        Log::warning('[PortalPush] Send failed', [
            'owner_id' => $sub->owner_id,
            'reason'   => $report->getReason(),
            'status'   => $statusCode,
        ]);
        return false;
    }

    /** Broadcast one payload to all of an owner's subscriptions for a category. */
    public function sendToOwner(int $ownerId, string $category, array $payload): int
    {
        $sent = 0;
        $subs = PortalPushSubscription::where('owner_id', $ownerId)->get();
        foreach ($subs as $sub) {
            if (! $sub->wantsCategory($category)) continue;
            if ($this->send($sub, $payload)) $sent++;
        }
        return $sent;
    }

    /**
     * Broadcast one payload to many owners (e.g. clinic-wide announcement,
     * specific pet-owner segment). Streams the subscriptions in chunks so
     * we never hold thousands of rows in memory at once.
     */
    public function sendToOwners(array $ownerIds, string $category, array $payload): int
    {
        if (empty($ownerIds)) return 0;
        $sent = 0;
        PortalPushSubscription::whereIn('owner_id', array_values(array_unique($ownerIds)))
            ->chunk(200, function ($subs) use ($category, $payload, &$sent) {
                foreach ($subs as $sub) {
                    if (! $sub->wantsCategory($category)) continue;
                    if ($this->send($sub, $payload)) $sent++;
                }
            });
        return $sent;
    }

    /**
     * Broadcast to EVERY subscription in the system for the given category.
     * Useful for clinic-wide announcements (set category='marketing' so
     * owners who opted out of marketing don't receive it).
     */
    public function broadcast(string $category, array $payload, ?int $businessId = null): int
    {
        $sent = 0;
        $q = PortalPushSubscription::query();
        if ($businessId !== null) {
            $q->whereHas('owner', fn ($o) => $o->where('business_id', $businessId));
        }
        $q->chunk(200, function ($subs) use ($category, $payload, &$sent) {
            foreach ($subs as $sub) {
                if (! $sub->wantsCategory($category)) continue;
                if ($this->send($sub, $payload)) $sent++;
            }
        });
        return $sent;
    }
}
