<?php

declare(strict_types=1);

require_once dirname(__DIR__, 2) . '/system/bootstrap.php';

$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
if ($method !== 'POST') {
    http_response_code(405);
    header('Allow: POST');
    return;
}

try {
    $input = myronAnalyticsCollectInput();

    if (!myronAnalyticsEnabled()) {
        http_response_code(204);
        return;
    }

    $pagePath = myronAnalyticsNormalizeCollectedPath((string) ($input['page_path'] ?? ''));
    if ($pagePath === '') {
        http_response_code(400);
        return;
    }

    $ua = (string) ($_SERVER['HTTP_USER_AGENT'] ?? '');
    if (myronAnalyticsIsBotUserAgent($ua)) {
        http_response_code(204);
        return;
    }

    $classifier = myronUserAgentClassifier();
    $classification = $classifier->classify($ua);
    $host = strtolower(trim((string) ($_SERVER['HTTP_HOST'] ?? parse_url(myronBuildPublicPageUrl('/'), PHP_URL_HOST) ?? '')));
    $sessionHash = myronAnalyticsSessionHashService()->computeSessionHash(
        myronAnalyticsClientIp(),
        (string) $classification['ua_family'],
        $host
    );

    myronSiteAnalyticsRepository()->recordPageview([
        'page_path' => $pagePath,
        'page_template_id' => myronAnalyticsResolvePageTemplateId($pagePath),
        'referrer_domain' => myronAnalyticsReferrerDomain((string) ($input['referrer'] ?? '')),
        'utm_source' => myronAnalyticsSafeText((string) ($input['utm_source'] ?? ''), 120),
        'utm_medium' => myronAnalyticsSafeText((string) ($input['utm_medium'] ?? ''), 120),
        'utm_campaign' => myronAnalyticsSafeText((string) ($input['utm_campaign'] ?? ''), 180),
        'utm_content' => myronAnalyticsSafeText((string) ($input['utm_content'] ?? ''), 180),
        'utm_term' => myronAnalyticsSafeText((string) ($input['utm_term'] ?? ''), 180),
        'session_hash' => $sessionHash,
        'device_type' => (string) $classification['device_type'],
        'browser_family' => (string) $classification['browser_family'],
        'os_family' => (string) $classification['os_family'],
        'country_code' => myronAnalyticsCountryCode(),
        'viewed_at' => myronNow(),
    ]);
} catch (InvalidArgumentException $exception) {
    $status = match ($exception->getMessage()) {
        'analytics_payload_too_large' => 413,
        'analytics_unsupported_media_type' => 415,
        default => 400,
    };
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('X-Content-Type-Options: nosniff');
    echo json_encode(['error' => $exception->getMessage()], JSON_UNESCAPED_SLASHES);
    return;
} catch (Throwable) {
    http_response_code(204);
    return;
}

http_response_code(204);

/**
 * @return array<string, mixed>
 */
function myronAnalyticsCollectInput(): array
{
    $raw = (string) file_get_contents('php://input');
    if ($raw === '') {
        if ($_POST !== []) {
            throw new InvalidArgumentException('analytics_unsupported_media_type');
        }
        return [];
    }
    if (strlen($raw) > 4096) {
        throw new InvalidArgumentException('analytics_payload_too_large');
    }
    if (!myronAnalyticsRequestHasJsonContentType()) {
        throw new InvalidArgumentException('analytics_unsupported_media_type');
    }
    if (preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $raw) === 1 || str_contains($raw, "\r") || str_contains($raw, "\n")) {
        throw new InvalidArgumentException('analytics_payload_invalid_control_chars');
    }
    $decoded = json_decode($raw, true);
    if (is_array($decoded)) {
        return myronAnalyticsSanitizeInputArray($decoded);
    }
    throw new InvalidArgumentException('analytics_payload_invalid_json');
}

function myronAnalyticsRequestHasJsonContentType(): bool
{
    $contentType = strtolower(trim((string) ($_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? '')));
    if ($contentType === '') {
        return false;
    }

    $mediaType = strtolower(trim(explode(';', $contentType, 2)[0]));
    return $mediaType === 'application/json' || str_ends_with($mediaType, '+json');
}

/**
 * @param array<mixed> $input
 * @return array<string, mixed>
 */
function myronAnalyticsSanitizeInputArray(array $input): array
{
    $sanitized = [];
    $recognizedKeys = [
        'page_path' => true,
        'referrer' => true,
        'utm_source' => true,
        'utm_medium' => true,
        'utm_campaign' => true,
        'utm_content' => true,
        'utm_term' => true,
    ];

    foreach ($input as $key => $value) {
        if (!is_string($key)) {
            continue;
        }
        if (!is_scalar($value) && $value !== null) {
            if (isset($recognizedKeys[$key]) || str_starts_with($key, 'utm_')) {
                throw new InvalidArgumentException('analytics_payload_invalid_type');
            }
            continue;
        }
        $stringValue = (string) $value;
        if (preg_match('/[\x00-\x1F\x7F]/', $stringValue) === 1) {
            throw new InvalidArgumentException('analytics_payload_invalid_control_chars');
        }
        if (($key === 'referrer' || str_starts_with($key, 'utm_')) && myronAnalyticsTextContainsHtmlPayload($stringValue)) {
            throw new InvalidArgumentException('analytics_payload_invalid_html');
        }
        $sanitized[$key] = $stringValue;
    }
    return $sanitized;
}

function myronAnalyticsTextContainsHtmlPayload(string $value): bool
{
    $value = trim($value);
    if ($value === '') {
        return false;
    }

    if (preg_match('/[<>"\']/', $value) === 1) {
        return true;
    }

    return preg_match('/^\s*(?:javascript|data):/i', $value) === 1;
}

function myronAnalyticsNormalizeCollectedPath(string $path): string
{
    if ($path === '' || preg_match('/[\x00-\x1F\x7F<>]/', $path) === 1) {
        throw new InvalidArgumentException('analytics_path_invalid');
    }
    $parts = parse_url($path);
    if (is_array($parts)) {
        $path = (string) ($parts['path'] ?? '');
        $query = (string) ($parts['query'] ?? '');
        if ($query !== '') {
            parse_str($query, $queryParams);
            $utm = [];
            foreach (['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'] as $key) {
                if (isset($queryParams[$key]) && is_scalar($queryParams[$key])) {
                    $utm[$key] = myronAnalyticsSafeText((string) $queryParams[$key], 180);
                }
            }
            if ($utm !== []) {
                $path .= '?' . http_build_query($utm);
            }
        }
    }
    $path = '/' . ltrim(trim($path), '/');
    $path = preg_replace('#/+#', '/', $path) ?: '/';
    if (strlen($path) > 1000 || preg_match('#^/[A-Za-z0-9._~/%?&=+-]*$#', $path) !== 1) {
        throw new InvalidArgumentException('analytics_path_invalid');
    }
    return $path;
}

function myronAnalyticsSafeText(string $value, int $maxLength): string
{
    $value = trim($value);
    if ($value === '') {
        return '';
    }
    if (preg_match('/[\x00-\x1F\x7F<>]/', $value) === 1) {
        throw new InvalidArgumentException('analytics_payload_invalid_control_chars');
    }
    return substr($value, 0, $maxLength);
}

function myronAnalyticsIsBotUserAgent(string $userAgent): bool
{
    $ua = strtolower($userAgent);
    foreach (['bot', 'crawl', 'spider', 'slurp', 'fetch', 'preview', 'scan', 'checker', 'monitor', 'headless', 'lighthouse'] as $pattern) {
        if (str_contains($ua, $pattern)) {
            return true;
        }
    }
    return false;
}

function myronAnalyticsClientIp(): string
{
    foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
        $value = trim((string) ($_SERVER[$key] ?? ''));
        if ($value === '') {
            continue;
        }
        return trim(explode(',', $value)[0]);
    }
    return '';
}

function myronAnalyticsReferrerDomain(string $referrer): string
{
    if ($referrer === '') {
        return '';
    }
    if (preg_match('/[\x00-\x1F\x7F]/', $referrer) === 1 || myronAnalyticsTextContainsHtmlPayload($referrer)) {
        throw new InvalidArgumentException('analytics_payload_invalid_html');
    }
    $host = parse_url($referrer, PHP_URL_HOST);
    if (!is_string($host)) {
        return '';
    }
    $host = strtolower($host);
    return preg_match('/^[a-z0-9.-]{1,255}$/', $host) === 1 ? $host : '';
}

function myronAnalyticsCountryCode(): string
{
    $country = strtoupper(trim((string) ($_SERVER['HTTP_CF_IPCOUNTRY'] ?? '')));
    if (preg_match('/^[A-Z]{2}$/', $country) === 1) {
        return $country;
    }
    if (function_exists('geoip_country_code_by_name')) {
        try {
            $geo = strtoupper(trim((string) geoip_country_code_by_name(myronAnalyticsClientIp())));
            return preg_match('/^[A-Z]{2}$/', $geo) === 1 ? $geo : '';
        } catch (Throwable) {
            return '';
        }
    }
    return '';
}

function myronAnalyticsResolvePageTemplateId(string $path): string
{
    $statement = myronDatabase()->prepare('SELECT template_id FROM pages WHERE path = :path LIMIT 1');
    $statement->execute([':path' => $path]);
    $templateId = $statement->fetchColumn();
    return is_string($templateId) ? $templateId : '';
}
