<?php
declare(strict_types=1);

/**
 * /functional/security/ip_rules.php
 * Admin actions for IP rules:
 * - add
 * - delete
 * - import (bulk)
 */

ob_start();

$__wantJson =
    (strtolower((string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'fetch') ||
    (strtolower((string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest') ||
    (strpos(strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? '')), 'application/json') !== false) ||
    ((isset($_POST['ajax']) && (string)$_POST['ajax'] === '1')) ||
    ((isset($_GET['ajax'])  && (string)$_GET['ajax']  === '1'));

if ($__wantJson) {
  @ini_set('display_errors', '0'); // щоб warning/notice не ламали JSON
}

require_once __DIR__ . '/../../includes/start.php';
require_role(['admin']);

$pdo = db();
$me  = current_user();
$meId = (int)($me['id'] ?? 0);

require_once __DIR__ . '/../../includes/security_ip_rules.php';

/* tiny SEO/robots safety for functional endpoint */
if (!headers_sent()) {
  header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet', true);
}

/* polyfills for PHP < 8 */
if (!function_exists('str_starts_with')) {
  function str_starts_with(string $haystack, string $needle): bool {
    if ($needle === '') return true;
    return strncmp($haystack, $needle, strlen($needle)) === 0;
  }
}
if (!function_exists('str_contains')) {
  function str_contains(string $haystack, string $needle): bool {
    if ($needle === '') return true;
    return strpos($haystack, $needle) !== false;
  }
}

/* fallbacks */
if (!function_exists('is_post')) {
  function is_post(): bool { return (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'); }
}
if (!function_exists('post_str')) {
  function post_str(string $k, string $d=''): string {
    $v = $_POST[$k] ?? null;
    if ($v === null) return $d;
    if (is_array($v)) return $d;
    return trim((string)$v);
  }
}
if (!function_exists('post_int')) {
  function post_int(string $k, int $d=0): int {
    $v = $_POST[$k] ?? null;
    if ($v === null) return $d;
    if (is_array($v)) return $d;
    return (int)$v;
  }
}
if (!function_exists('post_str_any')) {
  function post_str_any(array $keys, string $d=''): string {
    foreach ($keys as $k) {
      $v = $_POST[$k] ?? null;
      if ($v === null) continue;
      if (is_array($v)) continue;
      $s = trim((string)$v);
      if ($s !== '') return $s;
    }
    return $d;
  }
}

/* json helper */
if (!function_exists('json_out')) {
  function json_out(array $data, int $code = 200): void {
    while (ob_get_level() > 0) { @ob_end_clean(); }

    if (!headers_sent()) {
      header('Content-Type: application/json; charset=UTF-8');
      header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
      header('Pragma: no-cache');
    }
    http_response_code($code);
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
  }
}

/**
 * admin_tt fallback
 */
if (!function_exists('admin_tt')) {
  function admin_tt(string $key, string $uk, string $ru, string $en): string {
    if (function_exists('t')) {
      $v = (string)t($key);
      if ($v !== '' && $v !== $key) return $v;
    }

    $l = 'uk';
    if (function_exists('lang')) {
      $l = (string)lang();
    } elseif (!empty($_SESSION['lang']) && is_string($_SESSION['lang'])) {
      $l = (string)$_SESSION['lang'];
    } elseif (!empty($GLOBALS['mv_lang']) && is_string($GLOBALS['mv_lang'])) {
      $l = (string)$GLOBALS['mv_lang'];
    }

    if (!in_array($l, ['uk','ru','en'], true)) $l = 'uk';

    return match ($l) {
      'ru' => $ru,
      'en' => $en,
      default => $uk,
    };
  }
}

/* CSRF: accept different field names + fallback check */
if (!function_exists('csrf_any_check')) {
  function csrf_any_check(string $token): bool {
    $token = trim($token);
    if ($token === '') return false;

    if (function_exists('csrf_check')) {
      try {
        if ((bool)csrf_check($token)) return true;
      } catch (Throwable $e) {
        // ignore -> fallback
      }
    }

    $candidates = [];

    foreach (['csrf', 'csrf_token', '_csrf', '_token', 'admin_csrf'] as $k) {
      if (isset($_SESSION[$k]) && is_string($_SESSION[$k]) && $_SESSION[$k] !== '') {
        $candidates[] = (string)$_SESSION[$k];
      }
    }

    if (isset($_SESSION['_csrf']) && is_array($_SESSION['_csrf'])) {
      if (isset($_SESSION['_csrf']['token']) && is_string($_SESSION['_csrf']['token']) && $_SESSION['_csrf']['token'] !== '') {
        $candidates[] = (string)$_SESSION['_csrf']['token'];
      }
    }

    foreach ($candidates as $cand) {
      if (function_exists('hash_equals')) {
        if (hash_equals($cand, $token)) return true;
      } else {
        if ($cand === $token) return true;
      }
    }

    return false;
  }
}

$back = '/admin/security.php#ip';

if (!is_post()) {
  if ($__wantJson) json_out(['ok' => false, 'msg' => 'Method Not Allowed'], 405);
  header('Location: ' . $back);
  exit;
}

$csrf = post_str_any(['csrf', 'csrf_token', '_csrf', '_token'], '');
if (!csrf_any_check($csrf)) {
  $m = admin_tt('csrf_err','Помилка безпеки (CSRF).','Ошибка безопасности (CSRF).','Security error (CSRF).');
  if ($__wantJson) json_out(['ok' => false, 'msg' => $m], 400);

  flash_set('err', $m);
  header('Location: ' . $back);
  exit;
}

$action = post_str('action');

if ($action === 'add') {
  $ipOrCidr = post_str('ip_or_cidr');
  $scope    = post_str('scope', 'both');
  $reason   = post_str('reason');

  $blockLogin = ($scope === 'login' || $scope === 'both');
  $blockReg   = ($scope === 'register' || $scope === 'both');

  [$ok, $msg] = security_ip_rules_add($pdo, $ipOrCidr, $blockLogin, $blockReg, $reason, $meId);

  if ($ok) {
    $m = admin_tt('sec_ip_added','IP/CIDR додано.','IP/CIDR добавлен.','IP/CIDR added.');
    if ($__wantJson) json_out(['ok' => true, 'msg' => $m]);
    flash_set('ok', $m);
  } else {
    $human = $msg;
    if ($msg === 'Invalid IP')   $human = admin_tt('sec_ip_bad','Некоректний IP.','Некорректный IP.','Invalid IP.');
    if ($msg === 'Invalid CIDR') $human = admin_tt('sec_cidr_bad','Некоректний CIDR (поки що тільки IPv4 CIDR).','Некорректный CIDR (пока только IPv4 CIDR).','Invalid CIDR (IPv4 CIDR only for now).');
    if ($msg === 'Table not found') $human = admin_tt('sec_table_missing','Немає таблиці security_ip_rules.','Нет таблицы security_ip_rules.','security_ip_rules table missing.');

    if ($__wantJson) json_out(['ok' => false, 'msg' => $human], 400);
    flash_set('err', $human);
  }

  header('Location: ' . $back);
  exit;
}

if ($action === 'delete') {
  $id = post_int('id', 0);
  $ok = security_ip_rules_delete($pdo, $id);

  if ($ok) {
    $m = admin_tt('sec_ip_deleted','Правило видалено.','Правило удалено.','Rule deleted.');
    if ($__wantJson) json_out(['ok' => true, 'msg' => $m]);
    flash_set('ok', $m);
  } else {
    $m = admin_tt('sec_ip_delete_fail','Не вдалося видалити правило.','Не удалось удалить правило.','Failed to delete rule.');
    if ($__wantJson) json_out(['ok' => false, 'msg' => $m], 400);
    flash_set('err', $m);
  }

  header('Location: ' . $back);
  exit;
}

if ($action === 'import') {
  $text  = post_str('bulk_text');
  $scope = post_str('scope', 'both');
  $reasonCommon = post_str('reason');

  $blockLogin = ($scope === 'login' || $scope === 'both');
  $blockReg   = ($scope === 'register' || $scope === 'both');

  $lines = preg_split('~\R+~u', $text) ?: [];
  $added = 0;
  $bad   = 0;

  foreach ($lines as $line) {
    $line = trim($line);
    if ($line === '') continue;
    if (str_starts_with($line, '#') || str_starts_with($line, ';')) continue;

    $rule = $line;
    $rReason = $reasonCommon;

    if (str_contains($line, '|')) {
      [$a, $b] = array_map('trim', explode('|', $line, 2));
      if ($a !== '') $rule = $a;
      if ($b !== '') $rReason = $b;
    }

    [$ok, ] = security_ip_rules_add($pdo, $rule, $blockLogin, $blockReg, $rReason, $meId);
    if ($ok) $added++; else $bad++;
  }

  if ($added > 0) {
    $m = admin_tt('sec_import_ok','Імпорт завершено. Додано: ','Импорт завершен. Добавлено: ','Import done. Added: ')
       . $added
       . ($bad ? ('; ' . admin_tt('sec_import_bad','помилок: ','ошибок: ','bad: ') . $bad) : '');

    if ($__wantJson) json_out(['ok' => true, 'msg' => $m, 'added' => $added, 'bad' => $bad]);
    flash_set('ok', $m);
  } else {
    $m = admin_tt('sec_import_none','Нічого не додано. Перевір формат.','Ничего не добавлено. Проверь формат.','Nothing added. Check format.');
    if ($__wantJson) json_out(['ok' => false, 'msg' => $m], 400);
    flash_set('err', $m);
  }

  header('Location: ' . $back);
  exit;
}

$m = admin_tt('sec_unknown_action','Невідома дія.','Неизвестное действие.','Unknown action.');
if ($__wantJson) json_out(['ok' => false, 'msg' => $m], 400);

flash_set('err', $m);
header('Location: ' . $back);
exit;