<?php
/**
 * Rewarded-ads "watch N ads to unlock M hours ad-free" endpoints.
 *
 * Endpoints (mounted by the front-controller router):
 *
 *   POST /api/user/grant-ad-free
 *       body: { "hours": float (default 2.0, max 24) }
 *       auth: required (Bearer JWT)
 *       returns: { "ad_free_until": ISO, "granted_hours": N, "seconds_remaining": int }
 *
 *   GET  /api/user/ad-free-status
 *       auth: required (Bearer JWT)
 *       returns: { "ad_free_until": ISO|null, "seconds_remaining": int, "active": bool }
 *
 * Storage: `users.ad_free_until` (VARCHAR ISO datetime, nullable).
 * Added idempotently by `ensure_schema()` in api/lib/db.php — see the
 * additive migration block. No data loss for existing rows.
 */
declare(strict_types=1);

function _ad_free_now_utc(): string {
    return gmdate('c'); // ISO 8601, UTC, e.g. 2026-05-21T14:09:49+00:00
}

function _ad_free_parse(?string $iso): ?DateTimeImmutable {
    if (!$iso) return null;
    try {
        return new DateTimeImmutable($iso);
    } catch (Throwable $e) {
        return null;
    }
}

function _ad_free_seconds_remaining(?string $until_iso): int {
    $until = _ad_free_parse($until_iso);
    if (!$until) return 0;
    $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
    $diff = $until->getTimestamp() - $now->getTimestamp();
    return $diff > 0 ? $diff : 0;
}

function route_user_grant_ad_free(): void {
    $user = require_auth(); // throws 401 if no valid JWT
    $body = read_json_body();
    $hours = (float)($body['hours'] ?? 2.0);
    if ($hours <= 0)   $hours = 2.0;
    if ($hours > 24.0) $hours = 24.0;

    // Admins get a no-op — they never see ads anyway.
    if (($user['role'] ?? '') === 'admin') {
        $row = db()->prepare('SELECT ad_free_until FROM users WHERE id = ?');
        $row->execute([$user['id']]);
        $r = $row->fetch();
        send_json([
            'ad_free_until'      => $r['ad_free_until'] ?? null,
            'granted_hours'      => 0.0,
            'seconds_remaining'  => _ad_free_seconds_remaining($r['ad_free_until'] ?? null),
        ]);
        return;
    }

    // Extend (don't overwrite). If existing window is in the past, start
    // from now so back-to-back rewards stack predictably.
    $sel = db()->prepare('SELECT ad_free_until FROM users WHERE id = ?');
    $sel->execute([$user['id']]);
    $current = $sel->fetch()['ad_free_until'] ?? null;
    $base = _ad_free_parse($current);
    $now  = new DateTimeImmutable('now', new DateTimeZone('UTC'));
    if (!$base || $base < $now) $base = $now;

    $secondsToAdd = (int)round($hours * 3600);
    $new_until    = $base->modify('+' . $secondsToAdd . ' seconds');
    $iso = $new_until->format(DateTimeInterface::ATOM);

    $upd = db()->prepare('UPDATE users SET ad_free_until = ? WHERE id = ?');
    $upd->execute([$iso, $user['id']]);

    send_json([
        'ad_free_until'      => $iso,
        'granted_hours'      => $hours,
        'seconds_remaining'  => _ad_free_seconds_remaining($iso),
    ]);
}

function route_user_ad_free_status(): void {
    $user = require_auth();
    $sel = db()->prepare('SELECT ad_free_until, user_ads_count FROM users WHERE id = ?');
    $sel->execute([$user['id']]);
    $row = $sel->fetch() ?: [];
    $iso = $row['ad_free_until'] ?? null;
    send_json([
        'ad_free_until'     => $iso,
        'seconds_remaining' => _ad_free_seconds_remaining($iso),
        'active'            => _ad_free_seconds_remaining($iso) > 0,
        'ads_count'         => (int)($row['user_ads_count'] ?? 0),
    ]);
}

/* Lightweight append-only log of rewarded-ad activity, powering the
 * admin "Ad Analytics" panel (ads watched today / unlocks / top users). */
function ad_events_ensure_schema(): void {
    static $done = false;
    if ($done) return;
    $done = true;
    db()->exec(
        "CREATE TABLE IF NOT EXISTS `ad_events` (
            `id`         BIGINT AUTO_INCREMENT PRIMARY KEY,
            `user_id`    VARCHAR(64)  NOT NULL,
            `type`       VARCHAR(16)  NOT NULL,
            `created_at` VARCHAR(40)  NOT NULL DEFAULT '',
            `created_ts` BIGINT       NOT NULL DEFAULT 0,
            INDEX `ix_ae_ts`   (`created_ts`),
            INDEX `ix_ae_user` (`user_id`),
            INDEX `ix_ae_type` (`type`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );
}

function ad_event_log(string $userId, string $type): void {
    try {
        ad_events_ensure_schema();
        db()->prepare('INSERT INTO ad_events (user_id, type, created_at, created_ts) VALUES (?,?,?,?)')
            ->execute([$userId, $type, gmdate('c'), time()]);
    } catch (Throwable $e) { /* analytics is best-effort */ }
}

/**
 * POST /api/user/ad-watched
 *   body: { "threshold": int (default 3, 1..10), "hours": float (default 1) }
 *   auth: required
 *
 * Server-authoritative rewarded-ad counter. Each call = ONE watched ad.
 * Increments `users.user_ads_count`; once it reaches `threshold` the
 * server grants `hours` ad-free (extending any active window) and resets
 * the counter to 0. This is the single source of truth — the client no
 * longer decides when to grant, which eliminates tampering + double
 * grants from racing SDK events.
 *
 * returns: { ads_count, threshold, granted, ad_free_until,
 *            seconds_remaining }
 */
function route_user_ad_watched(): void {
    $user = require_auth();
    $body = read_json_body();
    $threshold = (int)($body['threshold'] ?? 3);
    if ($threshold < 1)  $threshold = 3;
    if ($threshold > 10) $threshold = 10;
    $hours = (float)($body['hours'] ?? 1.0);
    if ($hours <= 0)   $hours = 1.0;
    if ($hours > 24.0) $hours = 24.0;

    // Admins never see ads — report a no-op steady state.
    if (($user['role'] ?? '') === 'admin') {
        $row = db()->prepare('SELECT ad_free_until FROM users WHERE id = ?');
        $row->execute([$user['id']]);
        $iso = $row->fetch()['ad_free_until'] ?? null;
        send_json([
            'ads_count' => 0, 'threshold' => $threshold, 'granted' => false,
            'ad_free_until' => $iso, 'seconds_remaining' => _ad_free_seconds_remaining($iso),
        ]);
        return;
    }

    // Atomic increment, then read back.
    db()->prepare('UPDATE users SET user_ads_count = user_ads_count + 1 WHERE id = ?')
        ->execute([$user['id']]);
    ad_event_log((string)$user['id'], 'watch');
    $sel = db()->prepare('SELECT ad_free_until, user_ads_count FROM users WHERE id = ?');
    $sel->execute([$user['id']]);
    $cur = $sel->fetch() ?: [];
    $count = (int)($cur['user_ads_count'] ?? 0);
    $iso   = $cur['ad_free_until'] ?? null;
    $granted = false;

    if ($count >= $threshold) {
        // Grant the reward — extend (never shorten) the ad-free window.
        $base = _ad_free_parse($iso);
        $now  = new DateTimeImmutable('now', new DateTimeZone('UTC'));
        if (!$base || $base < $now) $base = $now;
        $new_until = $base->modify('+' . (int)round($hours * 3600) . ' seconds');
        $iso = $new_until->format(DateTimeInterface::ATOM);
        // Reset the counter so the next unlock starts fresh.
        db()->prepare('UPDATE users SET ad_free_until = ?, user_ads_count = 0 WHERE id = ?')
            ->execute([$iso, $user['id']]);
        ad_event_log((string)$user['id'], 'unlock');
        $count = 0;
        $granted = true;
    }

    send_json([
        'ads_count'         => $count,
        'threshold'         => $threshold,
        'granted'           => $granted,
        'ad_free_until'     => $iso,
        'seconds_remaining' => _ad_free_seconds_remaining($iso),
    ]);
}
