<?php

namespace App\Services;

use App\Models\Event;
use App\Models\PushSubscription;
use Carbon\CarbonImmutable;
use Illuminate\Support\Arr;

class PushSubscriptionService
{
    public function register(Event $event, string $guestIdentifier, string $deviceId, array $payload): PushSubscription
    {
        $endpoint = (string) ($payload['endpoint'] ?? '');
        if ($endpoint === '') {
            throw new \InvalidArgumentException('Push endpoint missing.');
        }

        $keys = Arr::get($payload, 'keys', []);
        $publicKey = (string) ($keys['p256dh'] ?? '');
        $authToken = (string) ($keys['auth'] ?? '');

        if ($publicKey === '' || $authToken === '') {
            throw new \InvalidArgumentException('Push key material missing.');
        }

        $contentEncoding = (string) ($payload['content_encoding'] ?? Arr::get($payload, 'encoding', 'aes128gcm'));
        $language = (string) ($payload['language'] ?? null);
        $userAgent = (string) ($payload['user_agent'] ?? null);
        $expiresAt = $this->normalizeExpiration(Arr::get($payload, 'expiration_time'));

        $endpointHash = hash('sha256', $endpoint);
        $data = [
            'tenant_id' => $event->tenant_id,
            'event_id' => $event->getKey(),
            'guest_identifier' => $this->sanitizeIdentifier($guestIdentifier),
            'device_id' => $this->sanitizeIdentifier($deviceId) ?? 'anonymous',
            'public_key' => $publicKey,
            'auth_token' => $authToken,
            'content_encoding' => $contentEncoding ?: 'aes128gcm',
            'status' => 'active',
            'expires_at' => $expiresAt,
            'last_seen_at' => now(),
            'language' => $language !== '' ? substr($language, 0, 12) : null,
            'user_agent' => $userAgent !== '' ? substr($userAgent, 0, 255) : null,
            'failure_count' => 0,
        ];

        /** @var PushSubscription $subscription */
        $subscription = PushSubscription::query()
            ->where('endpoint_hash', $endpointHash)
            ->first();

        if ($subscription) {
            $subscription->fill($data);
            $subscription->status = 'active';
            $subscription->endpoint = $endpoint;
            $subscription->save();

            return $subscription;
        }

        return PushSubscription::create(array_merge($data, [
            'endpoint' => $endpoint,
            'endpoint_hash' => $endpointHash,
        ]));
    }

    public function revoke(Event $event, string $endpoint): bool
    {
        $hash = hash('sha256', (string) $endpoint);

        $subscription = PushSubscription::query()
            ->where('event_id', $event->getKey())
            ->where(function ($query) use ($hash, $endpoint) {
                $query->where('endpoint_hash', $hash)
                    ->orWhere('endpoint', $endpoint);
            })
            ->first();

        if (! $subscription) {
            return false;
        }

        $subscription->update([
            'status' => 'revoked',
            'last_failed_at' => now(),
        ]);

        return true;
    }

    public function markFailed(PushSubscription $subscription, ?string $message = null): void
    {
        $subscription->fill([
            'last_failed_at' => now(),
            'failure_count' => min(65535, $subscription->failure_count + 1),
        ]);

        if ($subscription->failure_count >= 3) {
            $subscription->status = 'revoked';
        }

        $meta = $subscription->meta ?? [];
        if ($message) {
            $meta['last_error'] = substr($message, 0, 255);
        }

        $subscription->meta = $meta;
        $subscription->save();
    }

    public function markDelivered(PushSubscription $subscription): void
    {
        $subscription->fill([
            'last_notified_at' => now(),
            'last_failed_at' => null,
            'failure_count' => 0,
        ])->save();
    }

    private function sanitizeIdentifier(?string $value): ?string
    {
        if ($value === null) {
            return null;
        }

        $sanitized = preg_replace('/[^A-Za-z0-9 _\-]/', '', $value) ?? '';
        $sanitized = trim(mb_substr($sanitized, 0, 120));

        return $sanitized === '' ? null : $sanitized;
    }

    private function normalizeExpiration(mixed $value): ?CarbonImmutable
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (is_numeric($value)) {
            // Push API reports milliseconds
            $seconds = (int) round(((float) $value) / 1000);

            return CarbonImmutable::createFromTimestampUTC($seconds);
        }

        try {
            return CarbonImmutable::parse((string) $value);
        } catch (\Throwable) {
            return null;
        }
    }
}
