<?php

declare(strict_types=1);

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');

$root = dirname(__DIR__);
require_once $root . '/lib/KClickClient.php';
require_once $root . '/lib/TrackerHttp.php';
require_once $root . '/lib/TrackerConfig.php';
require_once $root . '/lib/OpaqueTrackingToken.php';

function tracker_normalize_domain(string $domain): string
{
    $domain = strtolower(trim($domain));
    if ($domain === '') {
        return '';
    }
    if (str_starts_with($domain, 'http://') || str_starts_with($domain, 'https://')) {
        $host = parse_url($domain, PHP_URL_HOST);
        $domain = is_string($host) ? $host : '';
    }
    $domain = strtolower(trim($domain, " \t\n\r\0\x0B/"));
    if (str_contains($domain, ':')) {
        $host = parse_url('https://' . $domain, PHP_URL_HOST);
        $domain = is_string($host) ? $host : $domain;
    }

    return preg_replace('/^tracker\./i', '', $domain) ?? $domain;
}

function tracker_client_ip(array $server): string
{
    foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
        $value = trim((string) ($server[$key] ?? ''));
        if ($value === '') {
            continue;
        }
        if ($key === 'HTTP_X_FORWARDED_FOR') {
            $parts = array_map('trim', explode(',', $value));
            $value = (string) ($parts[0] ?? '');
        }
        if ($value !== '') {
            return $value;
        }
    }

    return '';
}

function tracker_safe_fallback(array $config): string
{
    $fallback = (string) ($config['fallback_url'] ?? '/');
    if ($fallback === '' || $fallback[0] !== '/' || (isset($fallback[1]) && $fallback[1] === '/')) {
        return '/';
    }

    return $fallback;
}

try {
    $config = TrackerConfig::load();
    $domain = tracker_normalize_domain((string) ($_SERVER['TRACKER_DOMAIN'] ?? $_SERVER['HTTP_HOST'] ?? ''));

    $path = (string) ($_SERVER['REQUEST_URI'] ?? '');
    $parsed = parse_url($path);
    $segments = array_values(array_filter(explode('/', (string) ($parsed['path'] ?? '')), 'strlen'));

    if (count($segments) < 1) {
        error_log('[tracker] 404 path=' . $path);
        http_response_code(404);
        exit;
    }

    $first = strtolower($segments[0]);

    $kind = '';
    $code = '';
    $directTarget = '';
    $cid = 0;
    $sid = 0;
    $lid = 0;
    $sig = '';
    $isLegacy = false;
    $isOpaque = false;

    if ($first === 'v1') {
        if (count($segments) !== 2) {
            error_log('[tracker] 404 invalid opaque path segments=' . count($segments));
            http_response_code(404);
            exit;
        }

        $token = (string) $segments[1];
        $codec = new OpaqueTrackingToken($config);
        if (!$codec->canDecode()) {
            error_log('[tracker] 503 opaque token keyring is empty');
            http_response_code(503);
            exit;
        }
        if (!$codec->isValidPublicToken($token)) {
            error_log('[tracker] 400 invalid opaque token syntax hash=' . substr(hash('sha256', $token), 0, 12));
            http_response_code(400);
            exit;
        }

        $opaquePayload = $codec->decode($domain, $token);
        if (!is_array($opaquePayload)) {
            error_log('[tracker] 403 opaque token decode failed hash=' . substr(hash('sha256', $token), 0, 12));
            http_response_code(403);
            exit;
        }

        $type = (string) ($opaquePayload['t'] ?? '');
        $cid = (int) ($opaquePayload['cid'] ?? 0);
        $sid = (int) ($opaquePayload['sid'] ?? 0);
        $lid = (int) ($opaquePayload['lid'] ?? 0);
        $isOpaque = true;

        if ($type === 'c') {
            $kind = 'r';
            $code = (string) ($opaquePayload['c'] ?? '');
        } elseif ($type === 'd') {
            $kind = 'r';
            $directTarget = (string) ($opaquePayload['u'] ?? '');
        } elseif ($type === 'o') {
            $kind = 'o';
        } else {
            error_log('[tracker] 400 unknown opaque token type=' . $type);
            http_response_code(400);
            exit;
        }
    } elseif ($first === 'r' || $first === 'o') {
        if (count($segments) < 2) {
            error_log('[tracker] 404 missing code path=' . $path);
            http_response_code(404);
            exit;
        }
        $kind = $first;
        $code = (string) $segments[1];
        $cid = isset($_GET['cid']) ? (int) $_GET['cid'] : 0;
        $sid = isset($_GET['sid']) ? (int) $_GET['sid'] : 0;
        $lid = isset($_GET['lid']) ? (int) $_GET['lid'] : 0;
        $sig = isset($_GET['sig']) ? (string) $_GET['sig'] : '';
    } elseif ($first === 'click') {
        $kind = 'r';
        $code = trim((string) ($_GET['code'] ?? ''));
        $cid = isset($_GET['cid']) ? (int) $_GET['cid'] : 0;
        $sid = isset($_GET['sid']) ? (int) $_GET['sid'] : 0;
        $lid = isset($_GET['lid']) ? (int) $_GET['lid'] : 0;
        $sig = isset($_GET['sig']) ? (string) $_GET['sig'] : '';
        $isLegacy = true;
    } elseif ($first === 'open') {
        $kind = 'o';
        $code = trim((string) ($_GET['code'] ?? ''));
        $cid = isset($_GET['cid']) ? (int) $_GET['cid'] : 0;
        $sid = isset($_GET['sid']) ? (int) $_GET['sid'] : 0;
        $lid = isset($_GET['lid']) ? (int) $_GET['lid'] : 0;
        $sig = isset($_GET['sig']) ? (string) $_GET['sig'] : '';
        $isLegacy = true;
    } else {
        error_log('[tracker] 404 unknown segment=' . $first);
        http_response_code(404);
        exit;
    }

    if ($code !== '' && !preg_match('/^[a-f0-9]{32}$/', $code)) {
        error_log('[tracker] 400 invalid code kind=' . $kind . ' code=' . substr($code, 0, 16));
        http_response_code(400);
        exit;
    }

    $urlParam = $isOpaque ? $directTarget : (isset($_GET['url']) ? (string) $_GET['url'] : '');

    if ($kind === 'r' && !$isLegacy && !$isOpaque && $code === '') {
        error_log('[tracker] 404 redirect missing code');
        http_response_code(404);
        exit;
    }

    $hmacSecret = (string) ($config['hmac_secret'] ?? '');
    $clientServerHmacSecret = (string) ($config['client_server_hmac_secret'] ?? '');

    if ($isOpaque) {
        if ($hmacSecret === '') {
            error_log('[tracker] 503 fail-closed: hmac_secret is empty (opaque)');
            http_response_code(503);
            exit;
        }
        if ($cid <= 0 || $sid <= 0 || $lid <= 0) {
            error_log('[tracker] 400 missing opaque ids kind=' . $kind . ' cid=' . $cid . ' sid=' . $sid . ' lid=' . $lid);
            http_response_code(400);
            exit;
        }
        if ($kind === 'r' && $code === '' && $urlParam === '') {
            error_log('[tracker] 400 opaque click missing code/url');
            http_response_code(400);
            exit;
        }
        $sig = hash_hmac('sha256', "{$kind}|{$code}|{$cid}|{$sid}|{$lid}", $hmacSecret);
    } elseif (!$isLegacy) {
        if ($hmacSecret === '') {
            error_log('[tracker] 503 fail-closed: hmac_secret is empty (non-legacy)');
            http_response_code(503);
            exit;
        }
        if ($cid <= 0 || $sid <= 0 || $lid <= 0) {
            error_log('[tracker] 400 missing ids kind=' . $kind . ' code=' . $code . ' cid=' . $cid . ' sid=' . $sid . ' lid=' . $lid);
            http_response_code(400);
            exit;
        }
        $expected = hash_hmac('sha256', "{$kind}|{$code}|{$cid}|{$sid}|{$lid}", $hmacSecret);
        if ($sig === '' || !hash_equals($expected, strtolower($sig))) {
            error_log('[tracker] 403 sig mismatch kind=' . $kind . ' code=' . $code);
            http_response_code(403);
            exit;
        }
    } elseif ($clientServerHmacSecret !== '' && $sig !== '') {
        // Legacy path: validate sig locally if provided to prevent DoS amplification.
        // Unsigned legacy public-link requests (no cid/sid/lid/sig) skip this block and rely on head-side decision.
        if ($cid > 0 && $sid > 0 && $lid > 0) {
            if ($kind === 'r') {
                $expected = hash_hmac('sha256', "click:{$cid}:{$sid}:{$lid}:{$code}", $clientServerHmacSecret);
            } else {
                $expected = hash_hmac('sha256', "open:{$cid}:{$sid}:{$lid}", $clientServerHmacSecret);
            }
            if (!hash_equals($expected, strtolower($sig))) {
                error_log('[tracker] 403 legacy sig mismatch kind=' . $kind . ' code=' . substr($code, 0, 8));
                http_response_code(403);
                exit;
            }
        }
    }

    $http = new TrackerHttp($config);

    if ($kind === 'r') {
        if ($code === '' && $urlParam === '') {
            error_log('[tracker] 400 click missing code/url');
            http_response_code(400);
            exit;
        }

        $payload = [
            'code' => $code,
            'domain' => $domain,
            'cid' => $cid,
            'sid' => $sid,
            'lid' => $lid,
            'sig' => $sig,
            'url' => $urlParam,
            'legacy' => $isLegacy ? 1 : 0,
            'opaque' => $isOpaque ? 1 : 0,
            'direct' => ($isOpaque && $code === '' && $urlParam !== '') ? 1 : 0,
            'ip' => tracker_client_ip($_SERVER),
            'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'ref' => $_SERVER['HTTP_REFERER'] ?? '',
            'cf_country' => $_SERVER['HTTP_CF_IPCOUNTRY'] ?? '',
            'ts' => time(),
        ];

        if (!empty($payload['direct'])) {
            $target = $urlParam;
            $routeSource = 'opaque_direct';
        } else {
            $resolved = $http->resolveTrackingTarget($payload);
            $target = (string) ($resolved['target_url'] ?? '');
            $routeSource = (string) ($resolved['route_source'] ?? 'remote');
        }

        if ($target === '' || !filter_var($target, FILTER_VALIDATE_URL)) {
            http_response_code(302);
            header('Location: ' . tracker_safe_fallback($config));
            exit;
        }

        http_response_code(302);
        header('Location: ' . $target);

        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }

        $keitaroAlreadyCounted = $routeSource === 'keitaro';
        if (!$keitaroAlreadyCounted && $code !== '' && class_exists('KClickClient') && !empty($config['keitaro_campaign_token'])) {
            try {
                $kc = new KClickClient((string) $config['keitaro_public_url'], (string) $config['keitaro_campaign_token']);
                $kc->sendAllParams();
                $kc->execute();
            } catch (\Throwable $e) {
            }
        }

        $http->reportClickEventAsync($payload + ['target_url' => $target, 'route_source' => $routeSource]);
        exit;
    }

    if ($kind === 'o') {
        $payload = [
            'code' => $code,
            'domain' => $domain,
            'cid' => $cid,
            'sid' => $sid,
            'lid' => $lid,
            'sig' => $sig,
            'legacy' => $isLegacy ? 1 : 0,
            'opaque' => $isOpaque ? 1 : 0,
            'ip' => tracker_client_ip($_SERVER),
            'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'ts' => time(),
        ];

        http_response_code(200);
        header('Content-Type: image/gif');
        echo base64_decode('R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');

        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }

        // Skip head call for legacy /open without attribution to avoid DoS amplification.
        if ($isLegacy && ($cid <= 0 || $sid <= 0 || $lid <= 0 || $sig === '')) {
            exit;
        }

        $http->reportOpenEventAsync($payload);
        exit;
    }

    http_response_code(404);
} catch (\Throwable $e) {
    error_log('[tracker] ' . $e->getMessage());
    http_response_code(500);
}
