<?php

declare(strict_types=1);

require_once __DIR__ . '/../redirect_payload.php';
require_once __DIR__ . '/../sanitize.php';

/** @var PDO $pdo */
$pdo = require __DIR__ . '/../connection.config.php';

$redirectPayload = srp_redirect_payload_decode($_GET['rk'] ?? null);

$clickId = meetup_sanitize_token($redirectPayload['click_id'] ?? ($_GET['click_id'] ?? null), 128);
$countryCode = meetup_sanitize_country_code($redirectPayload['country_code'] ?? ($_GET['country_code'] ?? null));
$userAgent = meetup_sanitize_token($redirectPayload['user_agent'] ?? ($_GET['user_agent'] ?? null), 32);
$ipAddress = meetup_sanitize_ip($redirectPayload['ip_address'] ?? ($_GET['ip_address'] ?? null));
$userLp = meetup_sanitize_token($redirectPayload['user_lp'] ?? ($_GET['user_lp'] ?? null), 64);

if ($clickId === null || $countryCode === null || $userAgent === null || $userLp === null) {
    meetup_fail(400);
}

$recordUrl = strtoupper($clickId);
$clickDate = date('Y-m-d');
$redirectToken = srp_redirect_payload_encode(
    $recordUrl,
    strtolower($countryCode),
    strtolower($userAgent),
    $ipAddress,
    strtolower($userLp),
);

// Dedup & cache directory
$dedupTtl  = 300; // 5 minutes
$dedupDir  = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR)
    . DIRECTORY_SEPARATOR . (defined('SRP_CACHE_DIR_NAME') ? SRP_CACHE_DIR_NAME : 'srp_bb');

// Lazy cache cleanup — sweep stale files ~1% of requests.
// Avoids unbounded disk growth even without cron.
if (random_int(0, 99) === 0) {
    meetup_sweep_cache($dedupDir);
}

try {
    // Dedup: skip click increment if same click_id+date+ip seen within 5 min.
    // Prevents accidental replay (bot re-crawl, refresh) and deliberate spam.
    $dedupKey  = 'click_' . md5($recordUrl . $clickDate . $ipAddress);
    $dedupFile = $dedupDir . DIRECTORY_SEPARATOR . $dedupKey . '.json';
    $isReplay  = is_file($dedupFile) && filemtime($dedupFile) > (time() - $dedupTtl);

    if (!$isReplay) {
        // clickrecord is provisioned by the installer (schema.sql); the previous
        // per-click information_schema existence probe was removed from this hot
        // path. A missing table now surfaces as a PDOException below → caught →
        // meetup_fail(500), the same client-visible outcome as before.
        $generateStatement = $pdo->prepare('SELECT 1 FROM generate WHERE sub_id = :sub_id LIMIT 1');
        $generateStatement->execute(['sub_id' => $recordUrl]);

        if ($generateStatement->fetchColumn() === false) {
            meetup_fail(404);
        }

        // Wrap UPDATE + INSERT in a transaction to prevent concurrent requests from
        // both seeing rowCount=0 and racing to INSERT, which would cause a duplicate key error.
        $pdo->beginTransaction();

        $updateStatement = $pdo->prepare(
            'UPDATE clickrecord SET clicks = clicks + 1 WHERE click_id = :click_id AND click_date = :click_date',
        );
        $updateStatement->execute([
            'click_id' => $recordUrl,
            'click_date' => $clickDate,
        ]);

        if ($updateStatement->rowCount() === 0) {
            $insertStatement = $pdo->prepare(
                'INSERT INTO clickrecord (click_id, clicks, leads, payout, click_date) '
                . 'VALUES (:click_id, 1, :leads, :payout, :click_date)',
            );
            $insertStatement->execute([
                'click_id' => $recordUrl,
                'leads' => '0',
                'payout' => '0',
                'click_date' => $clickDate,
            ]);
        }

        $pdo->commit();

        // Persist dedup marker
        if (!is_dir($dedupDir)) {
            @mkdir($dedupDir, 0700, true);
        }
        @touch($dedupFile);
    }
} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        try {
            $pdo->rollBack();
        } catch (Throwable) {
            // rollback failed — connection may be dead
        }
    }
    error_log('Meetup click pipeline failed: ' . $e->getMessage());
    meetup_fail(500);
}

header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Referrer-Policy: no-referrer');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Location: /_meetups/r.php?rk=' . rawurlencode($redirectToken), true, 302);
exit;

/**
 * Remove cache files older than $ttlSeconds from the SRP cache directory.
 * Called probabilistically (~1% of requests) for self-healing without cron.
 */
function meetup_sweep_cache(string $cacheDir, int $ttlSeconds = 86400): void
{
    if (!is_dir($cacheDir)) {
        return;
    }

    $files = @scandir($cacheDir);
    if (!is_array($files)) {
        return;
    }

    $cutoff = time() - $ttlSeconds;
    $scanned = 0;
    $maxScan = 200; // cap per-sweep to bound I/O on shared hosting

    foreach ($files as $file) {
        if ($file === '.' || $file === '..') {
            continue;
        }

        $path = $cacheDir . DIRECTORY_SEPARATOR . $file;

        if (!is_file($path)) {
            continue;
        }

        $scanned++;
        if ($scanned > $maxScan) {
            break;
        }

        $mtime = @filemtime($path);
        if ($mtime === false || $mtime >= $cutoff) {
            continue;
        }

        @unlink($path);
    }
}

function meetup_fail(int $statusCode): never
{
    http_response_code($statusCode);
    header('Content-Type: text/plain; charset=UTF-8');
    header('X-Content-Type-Options: nosniff');
    header('X-Frame-Options: DENY');
    header('Referrer-Policy: no-referrer');
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
    exit;
}
