<?php
declare(strict_types=1);

/**
 * /includes/security_antispam.php
 * Антиспам / антибот helper.
 *
 * Працює через:
 * - app_settings: налаштування
 * - security_antispam_flags: поточний статус/причина по user_id
 *
 * ВАЖЛИВО:
 * - не блокує створення постів/коментарів;
 * - тільки позначає акаунт у модерації;
 * - спроєктовано з fallback-ами, щоб не падати на старих збірках.
 */

if (!function_exists('antispam_table_exists')) {
    function antispam_table_exists(PDO $pdo, string $table): bool
    {
        try {
            $st = $pdo->prepare('SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1');
            $st->execute([$table]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('antispam_column_exists')) {
    function antispam_column_exists(PDO $pdo, string $table, string $column): bool
    {
        try {
            $st = $pdo->prepare('SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? LIMIT 1');
            $st->execute([$table, $column]);
            return (bool)$st->fetchColumn();
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('antispam_app_settings_has_table')) {
    function antispam_app_settings_has_table(PDO $pdo): bool
    {
        return antispam_table_exists($pdo, 'app_settings');
    }
}

if (!function_exists('antispam_flags_has_table')) {
    function antispam_flags_has_table(PDO $pdo): bool
    {
        return antispam_table_exists($pdo, 'security_antispam_flags');
    }
}

if (!function_exists('antispam_default_settings')) {
    function antispam_default_settings(): array
    {
        return [
            'enabled'                 => 1,
            'mark_status'             => 'bot',
            'new_account_window_min'  => 60,
            'post_limit'              => 5,
            'comment_limit'           => 10,
            'link_limit'              => 1,
            'duplicate_text_limit'    => 2,
            'stop_words'              => "whatsapp\ntelegram\nbitcoin\nforex\ncrypto\ncasino\nloan\nwork from home\ncontact me\nhttp://\nhttps://\nt.me/",
            'stop_words_posts'        => 1,
            'stop_words_comments'     => 1,
        ];
    }
}

if (!function_exists('antispam_setting_keys')) {
    function antispam_setting_keys(): array
    {
        return [
            'enabled',
            'mark_status',
            'new_account_window_min',
            'post_limit',
            'comment_limit',
            'link_limit',
            'duplicate_text_limit',
            'stop_words',
            'stop_words_posts',
            'stop_words_comments',
        ];
    }
}

if (!function_exists('antispam_setting_key_map')) {
    function antispam_setting_key_map(): array
    {
        return [
            'enabled'                => 'antispam_enabled',
            'mark_status'            => 'antispam_mark_status',
            'new_account_window_min' => 'antispam_new_account_window_min',
            'post_limit'             => 'antispam_post_limit',
            'comment_limit'          => 'antispam_comment_limit',
            'link_limit'             => 'antispam_link_limit',
            'duplicate_text_limit'   => 'antispam_duplicate_text_limit',
            'stop_words'             => 'antispam_stop_words',
            'stop_words_posts'       => 'antispam_stop_words_posts',
            'stop_words_comments'    => 'antispam_stop_words_comments',
        ];
    }
}

if (!function_exists('antispam_get_settings')) {
    function antispam_get_settings(PDO $pdo): array
    {
        $settings = antispam_default_settings();
        if (!antispam_app_settings_has_table($pdo)) {
            return $settings;
        }

        $map = antispam_setting_key_map();
        try {
            $keys = array_values($map);
            $in = implode(',', array_fill(0, count($keys), '?'));
            $st = $pdo->prepare("SELECT key_name, value_text FROM app_settings WHERE key_name IN ($in)");
            $st->execute($keys);
            $rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $rev = array_flip($map);
            foreach ($rows as $row) {
                $dbKey = (string)($row['key_name'] ?? '');
                $localKey = $rev[$dbKey] ?? null;
                if ($localKey === null) {
                    continue;
                }
                $value = (string)($row['value_text'] ?? '');
                switch ($localKey) {
                    case 'enabled':
                    case 'new_account_window_min':
                    case 'post_limit':
                    case 'comment_limit':
                    case 'link_limit':
                    case 'duplicate_text_limit':
                    case 'stop_words_posts':
                    case 'stop_words_comments':
                        $settings[$localKey] = (int)$value;
                        break;
                    case 'mark_status':
                        $settings[$localKey] = in_array($value, ['suspect', 'bot'], true) ? $value : 'bot';
                        break;
                    case 'stop_words':
                        $settings[$localKey] = $value;
                        break;
                }
            }
        } catch (Throwable $e) {
            return $settings;
        }

        return $settings;
    }
}

if (!function_exists('antispam_save_settings')) {
    function antispam_save_settings(PDO $pdo, array $input): void
    {
        if (!antispam_app_settings_has_table($pdo)) {
            throw new RuntimeException('app_settings table not found');
        }

        $defaults = antispam_default_settings();
        $settings = array_merge($defaults, $input);
        $settings['enabled'] = !empty($settings['enabled']) ? 1 : 0;
        $settings['stop_words_posts'] = !empty($settings['stop_words_posts']) ? 1 : 0;
        $settings['stop_words_comments'] = !empty($settings['stop_words_comments']) ? 1 : 0;
        $settings['new_account_window_min'] = max(1, (int)$settings['new_account_window_min']);
        $settings['post_limit'] = max(1, (int)$settings['post_limit']);
        $settings['comment_limit'] = max(1, (int)$settings['comment_limit']);
        $settings['link_limit'] = max(0, (int)$settings['link_limit']);
        $settings['duplicate_text_limit'] = max(0, (int)$settings['duplicate_text_limit']);
        $settings['mark_status'] = in_array((string)$settings['mark_status'], ['suspect', 'bot'], true) ? (string)$settings['mark_status'] : 'bot';
        $settings['stop_words'] = trim((string)$settings['stop_words']);

        $map = antispam_setting_key_map();

        $pdo->beginTransaction();
        try {
            $sql = 'INSERT INTO app_settings (key_name, value_text, updated_at) VALUES (?, ?, NOW())
                    ON DUPLICATE KEY UPDATE value_text = VALUES(value_text), updated_at = VALUES(updated_at)';
            $st = $pdo->prepare($sql);
            foreach ($map as $localKey => $dbKey) {
                $st->execute([$dbKey, (string)$settings[$localKey]]);
            }
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
    }
}

if (!function_exists('antispam_parse_stop_words')) {
    function antispam_parse_stop_words(string $text): array
    {
        $text = str_replace(["\r\n", "\r"], "\n", $text);
        $parts = preg_split('/\n+/', $text) ?: [];
        $out = [];
        foreach ($parts as $part) {
            $part = trim($part);
            if ($part === '') {
                continue;
            }
            $out[] = mb_strtolower($part, 'UTF-8');
        }
        return array_values(array_unique($out));
    }
}

if (!function_exists('antispam_normalize_text')) {
    function antispam_normalize_text(string $text): string
    {
        $text = trim($text);
        $text = preg_replace('/\s+/u', ' ', $text) ?? $text;
        return mb_strtolower($text, 'UTF-8');
    }
}

if (!function_exists('antispam_extract_urls')) {
    function antispam_extract_urls(string $text): array
    {
        $text = trim($text);
        if ($text === '') {
            return [];
        }
        preg_match_all('~\b((?:https?://|www\.)[^\s<]+)~iu', $text, $m);
        $urls = $m[1] ?? [];
        $out = [];
        foreach ($urls as $url) {
            $url = trim((string)$url, " \t\n\r\0\x0B.,;!?)('\\\"");
            if ($url !== '') {
                $out[] = $url;
            }
        }
        return array_values(array_unique($out));
    }
}

if (!function_exists('antispam_find_stop_words')) {
    function antispam_find_stop_words(array $stopWords, string $text): array
    {
        $haystack = antispam_normalize_text($text);
        if ($haystack === '') {
            return [];
        }
        $found = [];
        foreach ($stopWords as $word) {
            $word = antispam_normalize_text((string)$word);
            if ($word === '') {
                continue;
            }
            if (mb_stripos($haystack, $word, 0, 'UTF-8') !== false) {
                $found[] = $word;
            }
        }
        return array_values(array_unique($found));
    }
}

if (!function_exists('antispam_load_user')) {
    function antispam_load_user(PDO $pdo, int $userId): ?array
    {
        if ($userId <= 0 || !antispam_table_exists($pdo, 'users')) {
            return null;
        }
        try {
            $st = $pdo->prepare('SELECT id, username, email, created_at, is_active, status, about, website FROM users WHERE id = ? LIMIT 1');
            $st->execute([$userId]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('antispam_is_new_account')) {
    function antispam_is_new_account(?array $userRow, int $windowMin): bool
    {
        if (!$userRow) {
            return false;
        }
        $createdAt = trim((string)($userRow['created_at'] ?? ''));
        if ($createdAt === '') {
            return false;
        }
        $ts = strtotime($createdAt);
        if ($ts === false) {
            return false;
        }
        return $ts >= (time() - ($windowMin * 60));
    }
}

if (!function_exists('antispam_count_since')) {
    function antispam_count_since(PDO $pdo, string $table, int $userId, string $sinceSql): int
    {
        if ($userId <= 0 || !antispam_table_exists($pdo, $table)) {
            return 0;
        }
        try {
            $st = $pdo->prepare("SELECT COUNT(*) FROM `$table` WHERE user_id = ? AND created_at >= ?");
            $st->execute([$userId, $sinceSql]);
            return (int)$st->fetchColumn();
        } catch (Throwable $e) {
            return 0;
        }
    }
}

if (!function_exists('antispam_recent_duplicate_count')) {
    function antispam_recent_duplicate_count(PDO $pdo, string $table, int $userId, string $body, string $sinceSql): int
    {
        $body = trim($body);
        if ($userId <= 0 || $body === '' || !antispam_table_exists($pdo, $table)) {
            return 0;
        }
        try {
            $st = $pdo->prepare("SELECT COUNT(*) FROM `$table` WHERE user_id = ? AND created_at >= ? AND body = ?");
            $st->execute([$userId, $sinceSql, $body]);
            return (int)$st->fetchColumn();
        } catch (Throwable $e) {
            return 0;
        }
    }
}

if (!function_exists('antispam_recent_link_count')) {
    function antispam_recent_link_count(PDO $pdo, int $userId, string $sinceSql): int
    {
        if ($userId <= 0) {
            return 0;
        }

        $count = 0;
        foreach (['posts', 'comments'] as $table) {
            if (!antispam_table_exists($pdo, $table)) {
                continue;
            }
            try {
                $st = $pdo->prepare("SELECT body FROM `$table` WHERE user_id = ? AND created_at >= ? ORDER BY id DESC LIMIT 100");
                $st->execute([$userId, $sinceSql]);
                $rows = $st->fetchAll(PDO::FETCH_COLUMN) ?: [];
                foreach ($rows as $body) {
                    $count += count(antispam_extract_urls((string)$body));
                }
            } catch (Throwable $e) {
                // ignore
            }
        }

        return $count;
    }
}

if (!function_exists('antispam_flag_user')) {
    function antispam_flag_user(
        PDO $pdo,
        int $userId,
        string $status,
        string $reasonCode,
        string $reasonText,
        array $details = [],
        ?int $updatedBy = null,
        bool $isManual = false
    ): bool {
        if ($userId <= 0 || !antispam_flags_has_table($pdo)) {
            return false;
        }

        $status = in_array($status, ['suspect', 'bot'], true) ? $status : 'bot';
        $detailsJson = json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($detailsJson === false) {
            $detailsJson = '{}';
        }

        try {
            $sql = "INSERT INTO security_antispam_flags
                    (user_id, status, is_manual, reason_code, reason_text, details_json, hit_count, first_detected_at, last_detected_at, updated_at, updated_by)
                    VALUES (?, ?, ?, ?, ?, ?, 1, NOW(), NOW(), NOW(), ?)
                    ON DUPLICATE KEY UPDATE
                        status = VALUES(status),
                        is_manual = VALUES(is_manual),
                        reason_code = VALUES(reason_code),
                        reason_text = VALUES(reason_text),
                        details_json = VALUES(details_json),
                        hit_count = hit_count + 1,
                        last_detected_at = NOW(),
                        updated_at = NOW(),
                        updated_by = VALUES(updated_by)";
            $st = $pdo->prepare($sql);
            return $st->execute([
                $userId,
                $status,
                $isManual ? 1 : 0,
                $reasonCode,
                mb_substr($reasonText, 0, 255, 'UTF-8'),
                $detailsJson,
                $updatedBy,
            ]);
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('antispam_clear_user')) {
    function antispam_clear_user(PDO $pdo, int $userId): bool
    {
        if ($userId <= 0 || !antispam_flags_has_table($pdo)) {
            return false;
        }
        try {
            $st = $pdo->prepare('DELETE FROM security_antispam_flags WHERE user_id = ? LIMIT 1');
            return $st->execute([$userId]);
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('antispam_get_flag')) {
    function antispam_get_flag(PDO $pdo, int $userId): ?array
    {
        if ($userId <= 0 || !antispam_flags_has_table($pdo)) {
            return null;
        }
        try {
            $st = $pdo->prepare('SELECT * FROM security_antispam_flags WHERE user_id = ? LIMIT 1');
            $st->execute([$userId]);
            $row = $st->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('antispam_evaluate_content')) {
    function antispam_evaluate_content(PDO $pdo, int $userId, string $type, string $body): ?array
    {
        $settings = antispam_get_settings($pdo);
        if ((int)($settings['enabled'] ?? 0) !== 1) {
            return null;
        }

        $user = antispam_load_user($pdo, $userId);
        if (!$user) {
            return null;
        }

        $windowMin = max(1, (int)($settings['new_account_window_min'] ?? 60));
        if (!antispam_is_new_account($user, $windowMin)) {
            return null;
        }

        $sinceSql = date('Y-m-d H:i:s', time() - ($windowMin * 60));
        $status = (string)($settings['mark_status'] ?? 'bot');
        if (!in_array($status, ['suspect', 'bot'], true)) {
            $status = 'bot';
        }

        $body = trim($body);
        $urls = antispam_extract_urls($body);
        $stopWords = antispam_parse_stop_words((string)($settings['stop_words'] ?? ''));
        $foundStopWords = antispam_find_stop_words($stopWords, $body);

        if ($type === 'post') {
            $postsCount = antispam_count_since($pdo, 'posts', $userId, $sinceSql);
            if ($postsCount >= max(1, (int)($settings['post_limit'] ?? 5))) {
                return [
                    'status' => $status,
                    'reason_code' => 'posts_limit',
                    'reason_text' => 'Перевищено ліміт постів для нового акаунта',
                    'details' => ['posts_count' => $postsCount, 'window_min' => $windowMin],
                ];
            }

            if ((int)($settings['stop_words_posts'] ?? 1) === 1 && $foundStopWords) {
                return [
                    'status' => $status,
                    'reason_code' => 'stop_words_post',
                    'reason_text' => 'У пості знайдено стоп-слова',
                    'details' => ['matched' => $foundStopWords],
                ];
            }
        }

        if ($type === 'comment') {
            $commentsCount = antispam_count_since($pdo, 'comments', $userId, $sinceSql);
            if ($commentsCount >= max(1, (int)($settings['comment_limit'] ?? 10))) {
                return [
                    'status' => $status,
                    'reason_code' => 'comments_limit',
                    'reason_text' => 'Перевищено ліміт коментарів для нового акаунта',
                    'details' => ['comments_count' => $commentsCount, 'window_min' => $windowMin],
                ];
            }

            if ((int)($settings['stop_words_comments'] ?? 1) === 1 && $foundStopWords) {
                return [
                    'status' => $status,
                    'reason_code' => 'stop_words_comment',
                    'reason_text' => 'У коментарі знайдено стоп-слова',
                    'details' => ['matched' => $foundStopWords],
                ];
            }
        }

        $linkLimit = max(0, (int)($settings['link_limit'] ?? 1));
        if ($linkLimit > 0 && $urls) {
            $recentLinkCount = antispam_recent_link_count($pdo, $userId, $sinceSql);
            if ($recentLinkCount >= $linkLimit) {
                return [
                    'status' => $status,
                    'reason_code' => 'external_links_new_account',
                    'reason_text' => 'Новий акаунт активно публікує зовнішні посилання',
                    'details' => ['links_count' => $recentLinkCount, 'current_links' => $urls],
                ];
            }
        }

        $dupLimit = max(0, (int)($settings['duplicate_text_limit'] ?? 0));
        if ($dupLimit > 0 && $body !== '') {
            $dupCount = antispam_recent_duplicate_count($pdo, $type === 'post' ? 'posts' : 'comments', $userId, $body, $sinceSql);
            if ($dupCount >= $dupLimit) {
                return [
                    'status' => $status,
                    'reason_code' => 'duplicate_text',
                    'reason_text' => 'Новий акаунт повторює однаковий текст',
                    'details' => ['duplicate_count' => $dupCount],
                ];
            }
        }

        return null;
    }
}
