<?php
declare(strict_types=1);

/**
 * /functional/comment/_lib.php
 * Спільна бібліотека коментарів.
 *
 * Відповідає за:
 * - normalize body
 * - safe back URL
 * - перевірки таблиць/колонок
 * - безпечні upload-и вкладень
 * - cleanup файлів при rollback
 * - insert core для comments/comment_attachments
 * - безпечне нарахування VM Coins за створення коментаря
 * - Stage 3:
 *   - коректне тихе ігнорування штатних причин відмови VM Coins
 *     (денний ліміт, антиспам, дубльований контент, занадто часті дії)
 */

$__vmCoinsLib = __DIR__ . '/../VMCoins/_lib.php';
if (is_file($__vmCoinsLib)) {
    require_once $__vmCoinsLib;
}
unset($__vmCoinsLib);

if (!function_exists('comment_normalize_text')) {
    function comment_normalize_text(string $text): string
    {
        $text = str_replace(["\r\n", "\r"], "\n", $text);
        $text = preg_replace("/\n{3,}/", "\n\n", $text) ?? $text;
        return trim($text);
    }
}

if (!function_exists('comment_safe_back_url')) {
    function comment_safe_back_url(string $raw): string
    {
        $raw = trim($raw);
        if ($raw === '') return '/';

        if (preg_match('~^https?://~i', $raw)) {
            $p = parse_url($raw);
            $path = (string)($p['path'] ?? '/');
            $query = isset($p['query']) && $p['query'] !== '' ? ('?' . $p['query']) : '';
            $raw = $path . $query;
        }

        if ($raw === '' || $raw[0] !== '/') return '/';
        if (str_starts_with($raw, '//')) return '/';
        if (strpos($raw, "\0") !== false) return '/';

        return $raw;
    }
}

if (!function_exists('comment_table_exists')) {
    function comment_table_exists(PDO $pdo, string $table): bool
    {
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) return false;

        $sql = "
            SELECT 1
            FROM information_schema.tables
            WHERE table_schema = DATABASE()
              AND table_name = ?
            LIMIT 1
        ";
        $st = $pdo->prepare($sql);
        $st->execute([$table]);

        return (bool)$st->fetchColumn();
    }
}

if (!function_exists('comment_table_columns')) {
    function comment_table_columns(PDO $pdo, string $table): array
    {
        if (!comment_table_exists($pdo, $table)) return [];
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) return [];

        $rows = $pdo->query("DESCRIBE `{$table}`")->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $out = [];
        foreach ($rows as $r) {
            $f = strtolower((string)($r['Field'] ?? ''));
            if ($f !== '') $out[] = $f;
        }
        return $out;
    }
}

if (!function_exists('comment_has_column')) {
    function comment_has_column(PDO $pdo, string $table, string $column): bool
    {
        $cols = comment_table_columns($pdo, $table);
        return in_array(strtolower($column), $cols, true);
    }
}

if (!function_exists('comment_upload_root')) {
    function comment_upload_root(): string
    {
        return dirname(__DIR__, 2);
    }
}

if (!function_exists('comment_safe_segment')) {
    function comment_safe_segment(string $value, string $fallback = 'user'): string
    {
        $value = trim($value);
        $value = preg_replace('~[^a-zA-Z0-9._-]+~', '_', $value) ?? '';
        $value = trim($value, '._-');
        return $value !== '' ? $value : $fallback;
    }
}

if (!function_exists('comment_file_type_from_mime')) {
    function comment_file_type_from_mime(string $mime): string
    {
        $mime = strtolower(trim($mime));

        if (str_starts_with($mime, 'image/')) return 'image';
        if (str_starts_with($mime, 'audio/')) return 'audio';
        if (str_starts_with($mime, 'video/')) return 'video';

        return 'file';
    }
}

if (!function_exists('comment_safe_ext')) {
    function comment_safe_ext(string $name): string
    {
        $ext = strtolower((string)pathinfo($name, PATHINFO_EXTENSION));
        $ext = preg_replace('~[^a-z0-9]+~', '', $ext) ?? '';
        return $ext;
    }
}

if (!function_exists('comment_allowed_extensions')) {
    function comment_allowed_extensions(): array
    {
        return [
            'jpg','jpeg','png','gif','webp',
            'mp3','wav','ogg','m4a',
            'mp4','webm','mov',
            'pdf','txt','zip','rar','7z',
            'doc','docx','xls','xlsx','ppt','pptx'
        ];
    }
}

if (!function_exists('comment_allowed_mime_prefix')) {
    function comment_allowed_mime_prefix(string $mime): bool
    {
        $mime = strtolower(trim($mime));

        if (str_starts_with($mime, 'image/')) return true;
        if (str_starts_with($mime, 'audio/')) return true;
        if (str_starts_with($mime, 'video/')) return true;

        return in_array($mime, [
            'application/pdf',
            'text/plain',
            'application/zip',
            'application/x-zip-compressed',
            'application/x-rar-compressed',
            'application/vnd.rar',
            'application/x-7z-compressed',
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/vnd.ms-excel',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'application/vnd.ms-powerpoint',
            'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        ], true);
    }
}

if (!function_exists('comment_assert_upload_ok')) {
    function comment_assert_upload_ok(array $file): void
    {
        $err = (int)($file['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($err === UPLOAD_ERR_NO_FILE) {
            throw new RuntimeException('No file.');
        }
        if ($err !== UPLOAD_ERR_OK) {
            throw new RuntimeException('Upload error code: ' . $err);
        }
    }
}

if (!function_exists('comment_normalize_files_array')) {
    function comment_normalize_files_array(?array $files): array
    {
        if (!$files || !isset($files['name'])) return [];

        $names = $files['name'];
        if (!is_array($names)) {
            return [$files];
        }

        $out = [];
        $count = count($names);
        for ($i = 0; $i < $count; $i++) {
            $out[] = [
                'name'     => $files['name'][$i] ?? '',
                'type'     => $files['type'][$i] ?? '',
                'tmp_name' => $files['tmp_name'][$i] ?? '',
                'error'    => $files['error'][$i] ?? UPLOAD_ERR_NO_FILE,
                'size'     => $files['size'][$i] ?? 0,
            ];
        }

        return $out;
    }
}

if (!function_exists('comment_handle_uploads')) {
    function comment_handle_uploads(?array $files, string $username): array
    {
        $items = comment_normalize_files_array($files);
        if (!$items) return [];

        $root = comment_upload_root();
        $safeUser = comment_safe_segment($username, 'user');
        $year = date('Y');
        $month = date('m');

        $out = [];
        $finfo = new finfo(FILEINFO_MIME_TYPE);

        foreach ($items as $file) {
            $err = (int)($file['error'] ?? UPLOAD_ERR_NO_FILE);
            if ($err === UPLOAD_ERR_NO_FILE) continue;

            comment_assert_upload_ok($file);

            $tmp = (string)($file['tmp_name'] ?? '');
            $orig = (string)($file['name'] ?? '');
            $size = (int)($file['size'] ?? 0);

            if ($tmp === '' || !is_uploaded_file($tmp)) {
                throw new RuntimeException('Invalid uploaded file.');
            }

            if ($size <= 0) {
                throw new RuntimeException('Empty uploaded file.');
            }

            if ($size > 25 * 1024 * 1024) {
                throw new RuntimeException('File is too large.');
            }

            $mime = strtolower((string)$finfo->file($tmp));
            $ext  = comment_safe_ext($orig);

            if ($ext === '' || !in_array($ext, comment_allowed_extensions(), true)) {
                throw new RuntimeException('File extension is not allowed: ' . $orig);
            }

            if (!comment_allowed_mime_prefix($mime)) {
                throw new RuntimeException('File MIME is not allowed: ' . $mime);
            }

            if (str_starts_with($mime, 'image/')) {
                $img = @getimagesize($tmp);
                if ($img === false) {
                    throw new RuntimeException('Invalid image file: ' . $orig);
                }
            }

            $stored = date('Ymd_His') . '_' . bin2hex(random_bytes(6)) . '.' . $ext;
            $webPath = '/upload/' . $safeUser . '/comments/' . $year . '/' . $month . '/' . $stored;
            $absPath = $root . $webPath;
            $dir = dirname($absPath);

            if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
                throw new RuntimeException('Failed to create upload directory.');
            }

            if (!move_uploaded_file($tmp, $absPath)) {
                throw new RuntimeException('Failed to move uploaded file.');
            }

            $out[] = [
                'web_path'      => $webPath,
                'abs_path'      => $absPath,
                'stored_name'   => $stored,
                'original_name' => $orig,
                'mime'          => $mime,
                'size_bytes'    => $size,
                'file_type'     => comment_file_type_from_mime($mime),
            ];
        }

        return $out;
    }
}

if (!function_exists('comment_cleanup_uploaded_files')) {
    function comment_cleanup_uploaded_files(array $uploadedRows): void
    {
        foreach ($uploadedRows as $row) {
            $abs = (string)($row['abs_path'] ?? '');
            if ($abs !== '' && is_file($abs)) {
                @unlink($abs);
            }
        }
    }
}

if (!function_exists('comment_unlink_webpath')) {
    function comment_unlink_webpath(string $webPath): void
    {
        $webPath = trim($webPath);
        if ($webPath === '') return;

        if ($webPath[0] !== '/') {
            $webPath = '/' . $webPath;
        }

        if (!str_starts_with($webPath, '/upload/')) return;
        if (str_contains($webPath, '..')) return;

        $abs = comment_upload_root() . $webPath;
        $uploadRoot = realpath(comment_upload_root() . '/upload');
        $dir = realpath(dirname($abs));

        if ($uploadRoot === false || $dir === false) return;
        if (!str_starts_with($dir, $uploadRoot)) return;

        if (is_file($abs)) {
            @unlink($abs);
        }
    }
}

if (!function_exists('comment_find_post')) {
    function comment_find_post(PDO $pdo, int $postId): ?array
    {
        $st = $pdo->prepare("SELECT id, user_id, group_id FROM posts WHERE id = ? LIMIT 1");
        $st->execute([$postId]);
        $row = $st->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }
}

if (!function_exists('comment_parent_is_valid')) {
    function comment_parent_is_valid(PDO $pdo, int $postId, ?int $parentId): bool
    {
        if (!$parentId || $parentId <= 0) return true;

        $st = $pdo->prepare("SELECT id FROM comments WHERE id = ? AND post_id = ? LIMIT 1");
        $st->execute([$parentId, $postId]);

        return (bool)$st->fetchColumn();
    }
}

if (!function_exists('comment_insert_attachment_dynamic')) {
    function comment_insert_attachment_dynamic(PDO $pdo, int $commentId, array $row): void
    {
        if (!comment_table_exists($pdo, 'comment_attachments')) {
            throw new RuntimeException('comment_attachments table not found.');
        }

        $cols = comment_table_columns($pdo, 'comment_attachments');
        if (!$cols) {
            throw new RuntimeException('comment_attachments table has no columns.');
        }

        $data = [];

        if (in_array('comment_id', $cols, true))    $data['comment_id'] = $commentId;
        if (in_array('rel_path', $cols, true))      $data['rel_path'] = ltrim((string)$row['web_path'], '/');
        if (in_array('path', $cols, true))          $data['path'] = (string)$row['web_path'];
        if (in_array('stored_name', $cols, true))   $data['stored_name'] = (string)$row['stored_name'];
        if (in_array('original_name', $cols, true)) $data['original_name'] = (string)$row['original_name'];
        if (in_array('mime', $cols, true))          $data['mime'] = (string)$row['mime'];
        if (in_array('size_bytes', $cols, true))    $data['size_bytes'] = (int)$row['size_bytes'];
        if (in_array('file_type', $cols, true))     $data['file_type'] = (string)$row['file_type'];
        if (in_array('created_at', $cols, true))    $data['created_at'] = date('Y-m-d H:i:s');

        if (!isset($data['comment_id'])) {
            throw new RuntimeException('comment_attachments.comment_id column is required.');
        }
        if (!isset($data['rel_path']) && !isset($data['path'])) {
            throw new RuntimeException('comment_attachments path column is required.');
        }

        $fields = array_keys($data);
        $placeholders = implode(',', array_fill(0, count($fields), '?'));
        $fieldSql = '`' . implode('`,`', $fields) . '`';

        $sql = "INSERT INTO `comment_attachments` ({$fieldSql}) VALUES ({$placeholders})";
        $st = $pdo->prepare($sql);
        $st->execute(array_values($data));
    }
}

if (!function_exists('comment_try_reward_coins_for_comment')) {
    function comment_try_reward_coins_for_comment(PDO $pdo, int $userId, int $commentId, int $postId, string $body): void
    {
        if ($userId <= 0 || $commentId <= 0 || $postId <= 0) {
            return;
        }

        if (!function_exists('vmc_reward_comment_create')) {
            return;
        }

        try {
            $res = vmc_reward_comment_create($pdo, $userId, $commentId, $postId, $body);

            if (!is_array($res)) {
                return;
            }

            $ok = (bool)($res['ok'] ?? false);
            if ($ok) {
                return;
            }

            $reason = (string)($res['reason'] ?? '');
            $silentReasons = [
                'disabled',
                'schema_missing',
                'amount_zero',
                'comment_not_meaningful',
                'bad_input',
                'zero_delta',
                'daily_limit_reached',
                'too_fast',
                'duplicate_content',
                'spam_like_text',
            ];

            if (!in_array($reason, $silentReasons, true)) {
                error_log('comment_try_reward_coins_for_comment: ' . json_encode($res, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
            }
        } catch (Throwable $e) {
            error_log('comment_try_reward_coins_for_comment exception: ' . $e->getMessage());
        }
    }
}

if (!function_exists('comment_create_core')) {
    function comment_create_core(PDO $pdo, array $me, int $postId, string $body, ?int $parentId, array $uploadedRows): int
    {
        $body = comment_normalize_text($body);

        if ($postId <= 0) {
            throw new InvalidArgumentException('post_id required.');
        }

        if ($body === '' && !$uploadedRows) {
            throw new InvalidArgumentException('Comment body or attachment required.');
        }

        $post = comment_find_post($pdo, $postId);
        if (!$post) {
            throw new RuntimeException('Post not found.');
        }

        if (!comment_parent_is_valid($pdo, $postId, $parentId)) {
            throw new RuntimeException('Parent comment not found.');
        }

        $userId = (int)($me['id'] ?? 0);
        if ($userId <= 0) {
            throw new RuntimeException('User not found.');
        }

        $ownTx = !$pdo->inTransaction();

        try {
            if ($ownTx) {
                $pdo->beginTransaction();
            }

            $sql = "INSERT INTO comments (post_id, user_id, parent_id, body, created_at) VALUES (?, ?, ?, ?, ?)";
            $st = $pdo->prepare($sql);

            $st->execute([
                $postId,
                $userId,
                $parentId,
                $body,
                date('Y-m-d H:i:s'),
            ]);

            $commentId = (int)$pdo->lastInsertId();
            if ($commentId <= 0) {
                throw new RuntimeException('Comment insert failed.');
            }

            foreach ($uploadedRows as $row) {
                comment_insert_attachment_dynamic($pdo, $commentId, $row);
            }

            if ($ownTx && $pdo->inTransaction()) {
                $pdo->commit();
            }

            comment_try_reward_coins_for_comment($pdo, $userId, $commentId, $postId, $body);

            return $commentId;
        } catch (Throwable $e) {
            if ($ownTx && $pdo->inTransaction()) {
                $pdo->rollBack();
            }
            comment_cleanup_uploaded_files($uploadedRows);
            throw $e;
        }
    }
}