<?php
declare(strict_types=1);

function is_https(): bool
{
    if (!empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off') {
        return true;
    }
    if ((string)($_SERVER['SERVER_PORT'] ?? '') === '443') {
        return true;
    }
    $forwardedProto = strtolower(trim(explode(',', (string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? ''))[0] ?? ''));
    if ($forwardedProto === 'https') {
        return true;
    }
    $cfVisitor = (string)($_SERVER['HTTP_CF_VISITOR'] ?? '');
    return $cfVisitor !== '' && stripos($cfVisitor, 'https') !== false;
}

function base_url(string $path = ''): string
{
    $base = APP_URL;
    if ($base === '') {
        $scheme = is_https() ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? '';
        $base = $host ? $scheme . '://' . $host : '';
    }
    return rtrim($base, '/') . '/' . ltrim($path, '/');
}

function e(?string $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function ensure_dir(string $dir): void
{
    if (!is_dir($dir)) {
        mkdir($dir, 0775, true);
    }
}

function csrf_token(): string
{
    if (empty($_SESSION['_csrf'])) {
        $_SESSION['_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['_csrf'];
}

function verify_csrf(?string $token): bool
{
    return is_string($token) && !empty($_SESSION['_csrf']) && hash_equals($_SESSION['_csrf'], $token);
}


/**
 * Creates a short-lived, session-bound token for the public booking form.
 * The token prevents direct POST requests that did not first load the site.
 */
function booking_form_guard_fields(bool $allowImmediate = false): array
{
    $issuedAt = time();
    try {
        $token = bin2hex(random_bytes(32));
    } catch (Throwable $e) {
        $token = hash('sha256', uniqid((string)mt_rand(), true));
    }

    $tokens = is_array($_SESSION['_booking_form_tokens'] ?? null) ? $_SESSION['_booking_form_tokens'] : [];
    $keepSince = $issuedAt - 10800;
    foreach ($tokens as $key => $row) {
        $rowIssuedAt = is_array($row) ? (int)($row['issued_at'] ?? 0) : 0;
        if ($rowIssuedAt < $keepSince) {
            unset($tokens[$key]);
        }
    }
    if (count($tokens) > 30) {
        uasort($tokens, static fn(array $a, array $b): int => ((int)($a['issued_at'] ?? 0)) <=> ((int)($b['issued_at'] ?? 0)));
        $tokens = array_slice($tokens, -30, null, true);
    }

    $tokens[$token] = [
        'issued_at' => $issuedAt,
        'used_at' => 0,
        'allow_immediate' => $allowImmediate,
    ];
    $_SESSION['_booking_form_tokens'] = $tokens;

    $payload = $issuedAt . '|' . $token . '|' . session_id();
    $signature = hash_hmac('sha256', $payload, csrf_token());

    return [
        'issued_at' => (string)$issuedAt,
        'token' => $token,
        'signature' => $signature,
    ];
}

/**
 * Returns: valid, used, too_fast, expired or invalid.
 */
function booking_form_guard_status(string $issuedAtRaw, string $token, string $signature): string
{
    if (!preg_match('/^\d{9,12}$/', $issuedAtRaw) || !preg_match('/^[a-f0-9]{64}$/i', $token) || !preg_match('/^[a-f0-9]{64}$/i', $signature)) {
        return 'invalid';
    }

    $issuedAt = (int)$issuedAtRaw;
    $tokens = is_array($_SESSION['_booking_form_tokens'] ?? null) ? $_SESSION['_booking_form_tokens'] : [];
    $stored = is_array($tokens[$token] ?? null) ? $tokens[$token] : null;
    if ($stored === null || (int)($stored['issued_at'] ?? 0) !== $issuedAt) {
        return 'invalid';
    }

    $payload = $issuedAt . '|' . $token . '|' . session_id();
    $expected = hash_hmac('sha256', $payload, csrf_token());
    if (!hash_equals($expected, $signature)) {
        return 'invalid';
    }

    if ((int)($stored['used_at'] ?? 0) > 0) {
        return 'used';
    }

    $age = time() - $issuedAt;
    if ($age < 2 && empty($stored['allow_immediate'])) {
        return 'too_fast';
    }
    if ($age > 7200 || $age < 0) {
        return 'expired';
    }

    return 'valid';
}

function booking_form_guard_mark_used(string $token): void
{
    $tokens = is_array($_SESSION['_booking_form_tokens'] ?? null) ? $_SESSION['_booking_form_tokens'] : [];
    if (isset($tokens[$token]) && is_array($tokens[$token])) {
        $tokens[$token]['used_at'] = time();
        $_SESSION['_booking_form_tokens'] = $tokens;
    }
}

function request_method(): string
{
    return strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
}

function client_ip(): string
{
    foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
        if (!empty($_SERVER[$key])) {
            return trim(explode(',', (string)$_SERVER[$key])[0]);
        }
    }
    return '0.0.0.0';
}

function slugify(string $value): string
{
    if (function_exists('mb_strtolower')) {
        $value = mb_strtolower($value, 'UTF-8');
    } else {
        $value = strtolower($value);
    }
    $value = preg_replace('/[^\pL\pN]+/u', '-', $value) ?? '';
    $value = trim($value, '-');
    return $value ?: 'item';
}

function read_json_file(string $path, array $fallback = []): array
{
    if (!is_file($path)) {
        return $fallback;
    }
    $content = file_get_contents($path);
    if ($content === false) {
        return $fallback;
    }
    $data = json_decode($content, true);
    return is_array($data) ? $data : $fallback;
}

function write_json_file(string $path, array $data): bool
{
    ensure_dir(dirname($path));
    return file_put_contents(
        $path,
        json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        LOCK_EX
    ) !== false;
}

function redirect(string $to): void
{
    header('Location: ' . $to);
    exit;
}

function booking_client_message_default_title(): string
{
    return '✉️ Готовый текст для отправки клиенту';
}

function booking_client_message_default_template(): string
{
    return <<<'TEXT'
Здравствуйте, {name}!

Спасибо за заявку на бронирование домика {house} с {checkin_ru} по {checkout_ru}

Включено в стоимость:

Спальные места по вашему количеству гостей, постельное белье, подушки, полотенца, тапочки, зонт, пледы, коврики для йоги, гигиенические наборы (зубные щетки + пасты), вешалки для одежды, фен, жидкое мыло, бумажные полотенца, шампунь, гели для душа.
Наборы посуды, включая: чайник, чайный набор, кофе, микроволновую печь, сахар, соль, масло, специи, бутыль минеральной воды (19 л), штопор, мусорные пакеты.
Казан, мангал, решетка, шампура, дрова, розжиг (есть возможность запроса дополнительного комплекта).

Дополнительно:

Ранний заезд (12:00) и поздний выезд (18:00) — 3000 руб.
Баня с панорамным окном — 5000 руб.
Парение банщиком (1 час) — 3000 руб.
Купель Фурако — 6000 руб. (с добавлением хвои, цитрусов или молока +1000 руб.).
Массаж в домике (60 минут) — 4000 руб.
Романтическое или праздничное украшение домика (цветы + шарики + надпись) — 3000–6000 руб.
Прогулка на квадроциклах — от 3000 руб.
Прогулка на лошадях по лесным маршрутам (1,5 часа) — 4000 руб./чел.
Набор «Детский праздник» — 8000 руб.
Приватный бассейн на территории домика с подогревом — 5000 руб.

Подскажите, пожалуйста, нужны ли вам дополнительные услуги по домику?
TEXT;
}

function post_array(string $key): array
{
    $value = $_POST[$key] ?? [];
    return is_array($value) ? $value : [];
}

function post_text(string $key, string $default = ''): string
{
    $value = $_POST[$key] ?? $default;
    return is_string($value) ? trim($value) : $default;
}

function log_message(string $filename, string $message): void
{
    ensure_dir(LOGS_DIR);
    file_put_contents(LOGS_DIR . '/' . $filename, '[' . date('c') . '] ' . $message . PHP_EOL, FILE_APPEND);
}

function backup_file(string $path, string $prefix = 'backup'): void
{
    if (!is_file($path)) {
        return;
    }
    $timestamp = date('Ymd_His');
    $name = basename($path);
    $backupDir = DATA_DIR . '/backups/' . $prefix;
    ensure_dir($backupDir);
    copy($path, $backupDir . '/' . $timestamp . '_' . $name);
}

function format_bytes(int $bytes): string
{
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $size = max(0, $bytes);
    $index = 0;
    while ($size >= 1024 && $index < count($units) - 1) {
        $size /= 1024;
        $index++;
    }
    $precision = $index === 0 ? 0 : 1;
    return number_format($size, $precision, ',', ' ') . ' ' . $units[$index];
}
