<?php

declare(strict_types=1);

require_once dirname(__DIR__, 2) . '/core/helpers.php';
require_once dirname(__DIR__, 2) . '/core/auth.php';

function web_push_setting(string $key, string $default = ''): string
{
    try {
        $value = db_fetch_value(
            'SELECT setting_value FROM system_settings WHERE setting_key = :setting_key LIMIT 1',
            ['setting_key' => $key],
            $default
        );
    } catch (Throwable) {
        return $default;
    }

    return trim((string) $value);
}

function web_push_library_ready(): bool
{
    return is_file(app_root('node_modules/web-push/package.json'))
        && is_file(app_root('tools/webpush/generate-keys.mjs'))
        && is_file(app_root('tools/webpush/send-batch.mjs'));
}

function web_push_run_node_script(string $scriptPath, array $arguments = []): ?array
{
    if (!function_exists('proc_open')) {
        return null;
    }

    $command = 'node ' . escapeshellarg(app_root($scriptPath));
    foreach ($arguments as $argument) {
        $command .= ' ' . escapeshellarg($argument);
    }

    $process = proc_open(
        $command,
        [
            0 => ['pipe', 'r'],
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ],
        $pipes,
        app_root()
    );

    if (!is_resource($process)) {
        return null;
    }

    fclose($pipes[0]);
    $stdout = stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);

    $exitCode = proc_close($process);
    if ($exitCode !== 0) {
        return null;
    }

    $decoded = json_decode(trim((string) $stdout), true);
    return is_array($decoded) ? $decoded : null;
}

function web_push_default_subject(): string
{
    $contactEmail = trim((string) site_setting('contact_email', ''));

    if ($contactEmail !== '' && filter_var($contactEmail, FILTER_VALIDATE_EMAIL)) {
        return 'mailto:' . $contactEmail;
    }

    return absolute_base_url();
}

function web_push_ensure_settings(): array
{
    $settings = [
        'enabled' => web_push_setting('enable_web_push_notifications', '1'),
        'subject' => web_push_setting('web_push_vapid_subject', ''),
        'public_key' => web_push_setting('web_push_vapid_public_key', ''),
        'private_key' => web_push_setting('web_push_vapid_private_key', ''),
    ];

    if (!web_push_library_ready()) {
        return $settings;
    }

    $updates = [];

    if ($settings['subject'] === '') {
        $settings['subject'] = web_push_default_subject();
        $updates['web_push_vapid_subject'] = $settings['subject'];
    }

    if ($settings['public_key'] === '' || $settings['private_key'] === '') {
        $keys = web_push_run_node_script('tools/webpush/generate-keys.mjs');
        if (!is_array($keys)) {
            return $settings;
        }
        $settings['public_key'] = (string) ($keys['publicKey'] ?? '');
        $settings['private_key'] = (string) ($keys['privateKey'] ?? '');
        $updates['web_push_vapid_public_key'] = $settings['public_key'];
        $updates['web_push_vapid_private_key'] = $settings['private_key'];
    }

    if ($updates !== []) {
        foreach ($updates as $settingKey => $settingValue) {
            db_query(
                'INSERT INTO system_settings (setting_key, setting_value)
                 VALUES (:setting_key, :setting_value)
                 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)',
                ['setting_key' => $settingKey, 'setting_value' => $settingValue]
            );
        }
    }

    return $settings;
}

function web_push_config(): array
{
    $settings = web_push_ensure_settings();

    return [
        'enabled' => ($settings['enabled'] ?? '1') === '1',
        'subject' => (string) ($settings['subject'] ?? ''),
        'public_key' => (string) ($settings['public_key'] ?? ''),
        'private_key' => (string) ($settings['private_key'] ?? ''),
        'ready' => web_push_library_ready()
            && (string) ($settings['enabled'] ?? '1') === '1'
            && (string) ($settings['subject'] ?? '') !== ''
            && (string) ($settings['public_key'] ?? '') !== ''
            && (string) ($settings['private_key'] ?? '') !== '',
    ];
}

function web_push_enabled(): bool
{
    return (bool) (web_push_config()['ready'] ?? false);
}

function web_push_public_key(): string
{
    return (string) (web_push_config()['public_key'] ?? '');
}

function web_push_service_worker_url(): string
{
    return base_url('app/public/push-sw.js');
}

function web_push_subscribe_url(): string
{
    return base_url('app/public/web-push.php');
}

function web_push_upsert_subscription(array $subscription, ?int $userId, string $visitorKey, string $userAgent = ''): void
{
    $endpoint = trim((string) ($subscription['endpoint'] ?? ''));
    $keys = (array) ($subscription['keys'] ?? []);
    $publicKey = trim((string) ($keys['p256dh'] ?? ''));
    $authToken = trim((string) ($keys['auth'] ?? ''));

    if ($endpoint === '' || $publicKey === '' || $authToken === '') {
        throw new InvalidArgumentException('Push subscription payload is incomplete.');
    }

    db_query(
        'INSERT INTO web_push_subscriptions (
            endpoint_hash, endpoint, public_key, auth_token, content_encoding,
            user_id, visitor_key, user_agent, is_active, last_error, created_at, updated_at, last_seen_at
         ) VALUES (
            :endpoint_hash, :endpoint, :public_key, :auth_token, :content_encoding,
            :user_id, :visitor_key, :user_agent, 1, NULL, NOW(), NOW(), NOW()
         )
         ON DUPLICATE KEY UPDATE
            endpoint = VALUES(endpoint),
            public_key = VALUES(public_key),
            auth_token = VALUES(auth_token),
            content_encoding = VALUES(content_encoding),
            user_id = VALUES(user_id),
            visitor_key = VALUES(visitor_key),
            user_agent = VALUES(user_agent),
            is_active = 1,
            last_error = NULL,
            updated_at = NOW(),
            last_seen_at = NOW()',
        [
            'endpoint_hash' => hash('sha256', $endpoint),
            'endpoint' => $endpoint,
            'public_key' => $publicKey,
            'auth_token' => $authToken,
            'content_encoding' => trim((string) ($subscription['contentEncoding'] ?? 'aesgcm')) ?: 'aesgcm',
            'user_id' => $userId,
            'visitor_key' => $visitorKey,
            'user_agent' => $userAgent !== '' ? substr($userAgent, 0, 255) : null,
        ]
    );
}

function web_push_deactivate_subscription(string $endpoint, ?string $reason = null): void
{
    $endpoint = trim($endpoint);

    if ($endpoint === '') {
        return;
    }

    db_query(
        'UPDATE web_push_subscriptions
         SET is_active = 0,
             last_error = :last_error,
             updated_at = NOW()
         WHERE endpoint_hash = :endpoint_hash',
        [
            'last_error' => $reason !== null && $reason !== '' ? substr($reason, 0, 500) : null,
            'endpoint_hash' => hash('sha256', $endpoint),
        ]
    );
}

function web_push_active_subscriptions(): array
{
    if (!web_push_enabled()) {
        return [];
    }

    return db_fetch_all(
        'SELECT id, endpoint, public_key, auth_token, content_encoding
         FROM web_push_subscriptions
         WHERE is_active = 1
         ORDER BY updated_at DESC, id DESC'
    );
}

function web_push_icon_url(): ?string
{
    $logoPath = league_logo_path();

    if ($logoPath === null || $logoPath === '') {
        return null;
    }

    return absolute_url($logoPath);
}

function web_push_payload(array $notification): array
{
    $title = trim((string) ($notification['title'] ?? league_short_name()));
    $body = trim((string) ($notification['body'] ?? 'New update available.'));
    $url = trim((string) ($notification['url'] ?? absolute_url('app/public/index.php')));
    $tag = trim((string) ($notification['tag'] ?? 'jlfc-update'));
    $icon = trim((string) ($notification['icon'] ?? (web_push_icon_url() ?? '')));

    return [
        'title' => $title !== '' ? $title : league_short_name(),
        'body' => $body !== '' ? $body : 'New update available.',
        'url' => $url !== '' ? $url : absolute_url('app/public/index.php'),
        'tag' => $tag !== '' ? $tag : 'jlfc-update',
        'icon' => $icon !== '' ? $icon : null,
        'badge' => $icon !== '' ? $icon : null,
    ];
}

function web_push_broadcast(array $notification): array
{
    if (!web_push_enabled()) {
        return ['queued' => 0, 'success' => 0, 'failed' => 0];
    }

    $config = web_push_config();
    $payload = web_push_payload($notification);
    $subscriptions = web_push_active_subscriptions();

    if ($subscriptions === []) {
        return ['queued' => 0, 'success' => 0, 'failed' => 0];
    }

    $inputPath = tempnam(sys_get_temp_dir(), 'jlfc-webpush-in-');
    $outputPath = tempnam(sys_get_temp_dir(), 'jlfc-webpush-out-');

    if ($inputPath === false || $outputPath === false) {
        return ['queued' => 0, 'success' => 0, 'failed' => 0];
    }

    $batch = [
        'vapid' => [
            'subject' => (string) $config['subject'],
            'publicKey' => (string) $config['public_key'],
            'privateKey' => (string) $config['private_key'],
        ],
        'payload' => $payload,
        'subscriptions' => array_map(
            static fn(array $row): array => [
                'endpoint' => (string) $row['endpoint'],
                'publicKey' => (string) $row['public_key'],
                'authToken' => (string) $row['auth_token'],
                'contentEncoding' => (string) ($row['content_encoding'] ?: 'aesgcm'),
            ],
            $subscriptions
        ),
    ];

    file_put_contents($inputPath, json_encode($batch, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    $result = web_push_run_node_script('tools/webpush/send-batch.mjs', [$inputPath, $outputPath]);

    if ($result === null && is_file($outputPath)) {
        $decoded = json_decode((string) file_get_contents($outputPath), true);
        $result = is_array($decoded) ? $decoded : null;
    }

    if ($result === null) {
        @unlink($inputPath);
        @unlink($outputPath);
        return ['queued' => count($subscriptions), 'success' => 0, 'failed' => count($subscriptions)];
    }

    foreach ((array) ($result['failures'] ?? []) as $failure) {
        web_push_deactivate_subscription((string) ($failure['endpoint'] ?? ''), (string) ($failure['reason'] ?? 'Delivery failed'));
    }

    @unlink($inputPath);
    @unlink($outputPath);

    return [
        'queued' => (int) ($result['queued'] ?? count($subscriptions)),
        'success' => (int) ($result['success'] ?? 0),
        'failed' => (int) ($result['failed'] ?? 0),
    ];
}

function web_push_news_payload(int $postId): ?array
{
    $post = db_fetch_one(
        'SELECT title, slug, body, status, category, fixture_id, featured_image
         FROM news_posts
         WHERE id = :id
         LIMIT 1',
        ['id' => $postId]
    );

    if ($post === null || (string) ($post['status'] ?? '') !== 'published') {
        return null;
    }

    $body = trim(preg_replace('/\s+/', ' ', strip_tags((string) ($post['body'] ?? ''))) ?? '');
    $body = $body !== '' ? mb_strimwidth($body, 0, 140, '...') : 'Tap to read the latest league update.';
    $icon = null;

    if (!empty($post['featured_image'])) {
        $icon = absolute_url((string) $post['featured_image']);
    } elseif (web_push_icon_url() !== null) {
        $icon = web_push_icon_url();
    }

    return [
        'title' => (string) $post['title'],
        'body' => $body,
        'url' => absolute_url('app/public/news-details.php?slug=' . urlencode((string) $post['slug'])),
        'tag' => 'news-post-' . $postId,
        'icon' => $icon,
    ];
}

function web_push_send_news_post(int $postId): array
{
    $payload = web_push_news_payload($postId);

    if ($payload === null) {
        return ['queued' => 0, 'success' => 0, 'failed' => 0];
    }

    try {
        return web_push_broadcast($payload);
    } catch (Throwable) {
        return ['queued' => 0, 'success' => 0, 'failed' => 0];
    }
}
