<?php
// api/push-send.php — Web Push with proper VAPID JWT signing
require_once __DIR__ . '/../includes/config.php';

function base64url_encode(string $data): string {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode(string $data): string {
    return base64_decode(strtr($data, '-_', '+/') . str_repeat('=', (4 - strlen($data) % 4) % 4));
}

// ── VAPID JWT (ES256) ───────────────────────────────────────────────
function buildVapidJwt(string $endpoint): ?string {
    if (!defined('VAPID_PRIVATE_KEY') || !VAPID_PRIVATE_KEY) return null;

    $parts    = parse_url($endpoint);
    $audience = $parts['scheme'] . '://' . $parts['host'];

    $header  = base64url_encode(json_encode(['typ'=>'JWT','alg'=>'ES256']));
    $payload = base64url_encode(json_encode([
        'aud' => $audience,
        'exp' => time() + 43200,
        'sub' => VAPID_SUBJECT,
    ]));
    $unsigned = $header . '.' . $payload;

    // Build SEC1 EC private key PEM
    $privRaw = base64url_decode(VAPID_PRIVATE_KEY);
    if (strlen($privRaw) !== 32) return null;

    // SEC1 DER: SEQUENCE { version=1, privateKey, [0] namedCurve=P-256 }
    $sec1 = "\x30\x77\x02\x01\x01\x04\x20" . $privRaw .
            "\xa0\x0a\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07";
    $pem  = "-----BEGIN EC PRIVATE KEY-----\n" .
            chunk_split(base64_encode($sec1), 64, "\n") .
            "-----END EC PRIVATE KEY-----";

    $key = openssl_pkey_get_private($pem);
    if (!$key) return null;

    openssl_sign($unsigned, $derSig, $key, OPENSSL_ALGO_SHA256);

    // DER -> raw R||S
    $offset = 2;
    if (@ord($derSig[0]) !== 0x30) return null;
    $offset++;
    $rLen = ord($derSig[$offset++]);
    $r    = substr($derSig, $offset, $rLen); $offset += $rLen;
    $offset++;
    $sLen = ord($derSig[$offset++]);
    $s    = substr($derSig, $offset, $sLen);

    $r = str_pad(ltrim($r, "\x00"), 32, "\x00", STR_PAD_LEFT);
    $s = str_pad(ltrim($s, "\x00"), 32, "\x00", STR_PAD_LEFT);

    return $unsigned . '.' . base64url_encode($r . $s);
}

// ── Send Web Push (with or without encryption) ──────────────────────
function sendWebPush(array $sub, array $payload): bool {
    $endpoint = $sub['endpoint'];
    $body     = json_encode($payload);
    $jwt      = buildVapidJwt($endpoint);
    $pubKey   = defined('VAPID_PUBLIC_KEY') ? VAPID_PUBLIC_KEY : '';

    // Try encrypted send first
    $postData = '';
    $headers  = ['TTL: 86400'];

    if (!empty($sub['p256dh']) && !empty($sub['auth_key'])) {
        $enc = encryptAesgcm($body, $sub['p256dh'], $sub['auth_key']);
        if ($enc) {
            $headers = [
                'TTL: 86400',
                'Content-Type: application/octet-stream',
                'Content-Encoding: aesgcm',
                'Encryption: salt=' . $enc['salt'],
                'Crypto-Key: dh=' . $enc['dh'] . ($jwt ? ';p256ecdsa=' . $pubKey : ''),
            ];
            if ($jwt) $headers[] = 'Authorization: WebPush ' . $jwt;
            $postData = $enc['ciphertext'];
        }
    }

    if (!$postData) {
        // Unencrypted ping
        $headers = ['TTL: 86400', 'Content-Type: application/octet-stream'];
        if ($jwt) {
            $headers[] = 'Authorization: WebPush ' . $jwt;
            $headers[] = 'Crypto-Key: p256ecdsa=' . $pubKey;
        }
    }

    $ch = curl_init($endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $postData,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = curl_exec($ch);
    $code     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code === 410 || $code === 404) {
        global $pdo;
        $pdo && $pdo->prepare("DELETE FROM push_subscriptions WHERE endpoint=?")->execute([$endpoint]);
    }
    return ($code >= 200 && $code < 300);
}

// ── AES-GCM encryption (aesgcm draft) ──────────────────────────────
function encryptAesgcm(string $payload, string $p256dh, string $authKey) {
    try {
        $recvPub  = base64url_decode($p256dh);
        $authSec  = base64url_decode($authKey);
        if (strlen($recvPub) !== 65 || strlen($authSec) < 16) return false;

        // Generate local EC key pair
        $localKey = openssl_pkey_new(['curve_name'=>'prime256v1','private_key_type'=>OPENSSL_KEYTYPE_EC]);
        if (!$localKey) return false;
        $det = openssl_pkey_get_details($localKey);

        $lx = str_pad($det['ec']['x'], 32, "\x00", STR_PAD_LEFT);
        $ly = str_pad($det['ec']['y'], 32, "\x00", STR_PAD_LEFT);
        $localPub = "\x04" . $lx . $ly;

        // Build receiver public key PEM for ECDH
        $algSeq  = "\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07";
        $bitStr  = "\x03" . chr(strlen($recvPub) + 1) . "\x00" . $recvPub;
        $pubDer  = "\x30" . chr(strlen($algSeq) + strlen($bitStr)) . $algSeq . $bitStr;
        $pubPem  = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($pubDer), 64, "\n") . "-----END PUBLIC KEY-----";

        // ECDH
        if (!openssl_dh_compute_key($sharedSecret, $pubPem, $localKey)) return false;

        $salt = random_bytes(16);

        // HKDF extract+expand (aesgcm spec)
        $prkKey   = hash_hmac('sha256', $sharedSecret, $authSec . "Content-Encoding: auth\x00\x01", true);
        $context  = "P-256\x00" . chr(0) . chr(65) . $recvPub . chr(0) . chr(65) . $localPub;
        $cekInfo  = "Content-Encoding: aesgcm\x00" . $context;
        $nonceInfo= "Content-Encoding: nonce\x00" . $context;

        $prk   = hash_hmac('sha256', $prkKey, $salt . "\x01", true);
        $cek   = substr(hash_hmac('sha256', $prk, $cekInfo . "\x01", true), 0, 16);
        $nonce = substr(hash_hmac('sha256', $prk, $nonceInfo . "\x01", true), 0, 12);

        $padded     = "\x00\x00" . $payload;
        $tag        = '';
        $ciphertext = openssl_encrypt($padded, 'aes-128-gcm', $cek, OPENSSL_RAW_DATA, $nonce, $tag);
        if ($ciphertext === false) return false;

        return [
            'ciphertext' => $ciphertext . $tag,
            'salt'       => base64url_encode($salt),
            'dh'         => base64url_encode($localPub),
        ];
    } catch (Throwable $e) {
        error_log('Push encrypt: ' . $e->getMessage());
        return false;
    }
}

// ── Helpers ─────────────────────────────────────────────────────────
function notifyUser(PDO $pdo, int $userId, array $payload): int {
    $st = $pdo->prepare("SELECT endpoint, p256dh, auth_key FROM push_subscriptions WHERE user_id=?");
    $st->execute([$userId]);
    $n = 0;
    foreach ($st->fetchAll() as $sub) { if (sendWebPush($sub, $payload)) $n++; }
    return $n;
}
function notifyAll(PDO $pdo, array $payload): int {
    $st = $pdo->query("SELECT endpoint, p256dh, auth_key FROM push_subscriptions");
    $n  = 0;
    foreach ($st->fetchAll() as $sub) { if (sendWebPush($sub, $payload)) $n++; }
    return $n;
}

// ── Cron mode ────────────────────────────────────────────────────────
$isCron = php_sapi_name() === 'cli' ||
          (isset($_GET['cron_key']) && defined('CRON_KEY') && $_GET['cron_key'] === CRON_KEY);

if ($isCron) {
    $now  = new DateTime('now', new DateTimeZone('Asia/Colombo'));
    $time = $now->format('H:i');
    $day  = $now->format('D');

    $rems = $pdo->query("
        SELECT r.*, r.user_id as uid
        FROM reminders r
        INNER JOIN push_subscriptions ps ON ps.user_id = r.user_id
        WHERE r.is_active = 1
        GROUP BY r.id
    ")->fetchAll();

    $sent = 0;
    foreach ($rems as $r) {
        if (date('H:i', strtotime($r['reminder_time'])) === $time && in_array($day, json_decode($r['days'], true) ?: [])) {
            $sent += notifyUser($pdo, (int)$r['uid'], [
                'title' => '⏰ ' . $r['title'],
                'body'  => $r['message'] ?: 'Time to study! 📚',
                'icon'  => SITE_URL . '/images/studytrack.png',
                'badge' => SITE_URL . '/images/studytrack.png',
                'url'   => SITE_URL . '/reminders',
                'tag'   => 'reminder-' . $r['id'],
            ]);
        }
    }
    if (php_sapi_name() === 'cli') echo "[{$now->format('Y-m-d H:i:s')}] Sent $sent push notifications.\n";
    else { header('Content-Type: application/json'); echo json_encode(['sent' => $sent, 'time' => $time]); }
    exit;
}

// ── HTTP mode ────────────────────────────────────────────────────────
if (!session_id()) session_start();
requireLogin();
header('Content-Type: application/json');

$input  = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $input['action'] ?? '';

if ($action === 'send_test') {
    $n = notifyUser($pdo, (int)$_SESSION['user_id'], [
        'title' => '🎉 Push Notifications Active!',
        'body'  => 'StudyTrack reminders will now reach you even when the tab is closed. 📚',
        'icon'  => SITE_URL . '/images/studytrack.png',
        'badge' => SITE_URL . '/images/studytrack.png',
        'url'   => SITE_URL . '/reminders',
        'tag'   => 'test-' . time(),
    ]);
    echo json_encode(['success' => true, 'sent' => $n]);
    exit;
}

echo json_encode(['success' => false, 'message' => 'Unknown action']);
