<?php
namespace App\Controllers;

use App\Core\{Auth, Csrf, Database, Response};
use App\Models\{AdminLog, Artist, BankAccount, Category, Comment, CommunityPost, CreatorLedger, MediaFile, Mod, Music, Notification, Order, Page, PaymentProvider, Setting, User, WithdrawalRequest};
use App\Models\SecurityScanLog;
use App\Services\Security\ZaloroxShield;
use App\Services\Translate\ZaloroxTranslateBot;
use PDO;
use Throwable;
use function App\Core\url;

class ZPanelController extends BaseController
{
    public function dashboard(): string
    {
        $this->requireAdminPage();

        return $this->view('zpanel', 'zpanel/dashboard', $this->dashboardData());
    }

    public function section(?string $id = null): string
    {
        $this->requireAdminPage();
        $segments = $this->zpanelSegments();
        $section = $segments[0] ?? 'dashboard';
        $recordId = $id !== null ? (int) $id : (int) ($segments[1] ?? 0);

        if ($section === 'guvenlik-botu') {
            return $this->view('zpanel', 'zpanel/security-bot', $this->securityBotData());
        }

        if ($section === 'link-denetimi') {
            return $this->view('zpanel', 'zpanel/security-logs', $this->securityBotData() + ['title' => 'ZPanel - Link Denetimi', 'section' => 'link-denetimi']);
        }

        if ($section === 'yasakli-kelimeler') {
            return $this->view('zpanel', 'zpanel/forbidden-words', $this->securityBotData() + ['title' => 'ZPanel - Yasakli Kelimeler', 'section' => 'yasakli-kelimeler']);
        }

        if ($section === 'domain-listeleri') {
            return $this->view('zpanel', 'zpanel/domain-lists', $this->securityBotData() + ['title' => 'ZPanel - Domain Listeleri', 'section' => 'domain-listeleri']);
        }

        if (in_array($section, ['sanal-pos', 'payment-providers'], true)) {
            return $this->view('zpanel', 'zpanel/payment-providers', $this->paymentProviderData());
        }

        if ($section === 'kazanclar') {
            return $this->view('zpanel', 'zpanel/earnings', $this->earningsData());
        }

        if ($section === 'para-cekme-talepleri') {
            return $this->view('zpanel', 'zpanel/withdrawals', $this->withdrawalsData());
        }

        if ($section === 'site-banka-hesaplari') {
            return $this->view('zpanel', 'zpanel/site-bank-accounts', $this->siteBankData());
        }

        if ($section === 'finans-ayarlari') {
            return $this->view('zpanel', 'zpanel/financial-settings', $this->financialSettingsData());
        }

        if ($section === 'media') {
            return $this->view('zpanel', 'zpanel/media', $this->mediaData());
        }

        if ($section === 'test-araclari') {
            if (!$this->featureTestMode()) {
                return $this->view('zpanel', 'zpanel/dashboard', $this->dashboardData());
            }

            return $this->view('zpanel', 'zpanel/test-tools', $this->testToolsData());
        }

        return match ($section) {
            'kullanicilar' => $this->view('zpanel', 'zpanel/users', $this->usersData()),
            'modlar' => $this->view('zpanel', 'zpanel/mods', $this->modsData()),
            'muzikler' => $this->view('zpanel', 'zpanel/music', $this->musicData()),
            'community' => $this->view('zpanel', 'zpanel/community', $this->communityData()),
            'artist-basvurulari' => $this->view('zpanel', 'zpanel/artists', $this->artistsData()),
            'muzik-telif' => $this->view('zpanel', 'zpanel/music-copyright', $this->musicCopyrightData()),
            'kategoriler', 'mod-kategorileri', 'muzik-turleri' => $this->view('zpanel', 'zpanel/categories', $this->categoriesData($section)),
            'abonelikler' => $this->view('zpanel', 'zpanel/packages', $this->packagesData()),
            'odemeler' => $this->view('zpanel', 'zpanel/payments', $this->paymentsData()),
            'sayfalar' => $this->view('zpanel', 'zpanel/pages', $this->pagesData()),
            'sayfa-ekle' => $this->view('zpanel', 'zpanel/page-form', $this->pageFormData()),
            'sayfa-duzenle' => $this->view('zpanel', 'zpanel/page-form', $this->pageFormData($recordId)),
            'yorumlar' => $this->view('zpanel', 'zpanel/comments', $this->commentsData()),
            'mesaj-raporlari' => $this->view('zpanel', 'zpanel/message-reports', $this->messageReportsData()),
            'sosyal-kayitlar' => $this->view('zpanel', 'zpanel/social-records', $this->socialRecordsData()),
            'bildirimler' => $this->view('zpanel', 'zpanel/notifications', $this->notificationsData()),
            'ayarlar', 'settings' => $this->view('zpanel', 'zpanel/settings', $this->settingsData($section)),
            'dosya-yoneticisi' => $this->view('zpanel', 'zpanel/file-manager', $this->fileManagerData()),
            'raporlar' => $this->view('zpanel', 'zpanel/reports', $this->reportsData()),
            'loglar' => $this->view('zpanel', 'zpanel/logs', $this->logsData()),
            default => $this->view('zpanel', 'zpanel/dashboard', $this->dashboardData()),
        };
    }

    public function updatePaymentProviders(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }
        $payload = $this->payload();

        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        try {
            $provider = (new PaymentProvider())->updateFromAdmin($payload);
            (new AdminLog())->write('payment_provider.update', 'payment_provider', null, ['provider_key' => $provider['provider_key'] ?? $payload['provider_key'] ?? '']);

            return Response::json([
                'ok' => true,
                'message' => 'Sanal POS ayarlari kaydedildi.',
                'provider_key' => (string) ($provider['provider_key'] ?? ''),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.paymentProviders');

            return Response::json(['ok' => false, 'message' => 'Sanal POS ayarlari kaydedilemedi.'], 500);
        }
    }

    public function runTestTool(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        if (!$this->featureTestMode()) {
            return Response::json(['ok' => false, 'message' => 'FEATURE_TEST_MODE kapali.'], 403);
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $action = (string) ($payload['action'] ?? '');

        try {
            $pdo = Database::connection($this->config);
            $result = match ($action) {
                'test_mod' => $this->createTestMod($pdo),
                'test_music' => $this->createTestMusic($pdo, true),
                'test_music_without_audio' => $this->createTestMusic($pdo, false),
                'payment_success' => $this->createTestPayment($pdo, 'paid'),
                'payment_failed' => $this->createTestPayment($pdo, 'failed'),
                'subscription' => $this->createTestSubscription($pdo),
                'premium_access' => $this->createTestSubscription($pdo),
                'earning' => $this->createTestEarning($pdo),
                'withdrawal' => $this->createTestWithdrawal($pdo),
                'comment' => $this->createTestComment($pdo),
                'message' => $this->createTestMessage($pdo),
                'notification' => $this->createTestNotification($pdo),
                'security_scan' => $this->runSecurityScan((string) ($payload['url'] ?? 'https://malware-test.local/file.zip')),
                'reset_cookie_popup' => ['message' => 'Cookie popup localStorage/cookie reset komutu gonderildi.', 'client_reset' => ['zalorox_cookie_consent']],
                'reset_recommendations' => ['message' => 'Oneri gecmisi reset komutu gonderildi.', 'client_reset' => ['zalorox_recent_mods']],
                'reset_test_data' => $this->resetTestData($pdo),
                'reseed' => $this->runSeed(),
                default => ['message' => 'Bilinmeyen test aksiyonu.'],
            };

            return Response::json(['ok' => true] + $result);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.testTools');

            return Response::json(['ok' => false, 'message' => 'Test aksiyonu calistirilamadi.'], 500);
        }
    }

    private function securityBotData(): array
    {
        return [
            'title' => 'ZPanel - Guvenlik Botu',
            'section' => 'guvenlik-botu',
            'settings' => $this->shieldSettings(),
            'whitelist' => $this->listSecurityTable('domain_whitelist'),
            'blacklist' => $this->listSecurityTable('domain_blacklist'),
            'forbiddenWords' => $this->listSecurityTable('forbidden_words'),
            'logs' => $this->latestLogs(),
            'copyrightSummary' => $this->musicCopyrightSummary(),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function musicCopyrightSummary(): array
    {
        return [
            'total' => $this->safeScalar('SELECT COUNT(*) FROM music_copyright_checks'),
            'review' => $this->safeScalar('SELECT COUNT(*) FROM music_copyright_checks WHERE status IN ("review", "pending", "duplicate")'),
            'blocked' => $this->safeScalar('SELECT COUNT(*) FROM music_copyright_checks WHERE status = "blocked"'),
            'duplicates' => $this->safeScalar('SELECT COUNT(*) FROM music_copyright_checks WHERE status = "duplicate"'),
            'latest' => $this->queryRows('SELECT * FROM music_copyright_checks ORDER BY created_at DESC LIMIT 5'),
        ];
    }

    private function paymentProviderData(): array
    {
        try {
            $model = new PaymentProvider();

            return [
                'title' => 'ZPanel - Sanal POS Ayarlari',
                'section' => 'payment-providers',
                'providers' => $model->listForAdmin(),
                'knownProviders' => $model->knownProviders(),
                'csrfToken' => Csrf::token(),
            ];
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.paymentProviderData');

            return [
                'title' => 'ZPanel - Sanal POS Ayarlari',
                'section' => 'payment-providers',
                'providers' => [],
                'knownProviders' => [],
                'csrfToken' => Csrf::token(),
                'loadError' => 'Provider ayarlari yuklenemedi. Migration calistirilmis olmali.',
            ];
        }
    }

    private function earningsData(): array
    {
        try {
            $orders = new Order();

            return [
                'title' => 'ZPanel - Kazanclar',
                'section' => 'kazanclar',
                'summary' => $orders->adminEarningSummary(),
                'creators' => $orders->creatorSummaries(120),
                'sales' => $orders->recentSalesForAdmin(60),
                'ledger' => (new CreatorLedger())->recentForAdmin(80),
                'csrfToken' => Csrf::token(),
            ];
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.earningsData');

            return ['title' => 'ZPanel - Kazanclar', 'section' => 'kazanclar', 'summary' => [], 'creators' => [], 'sales' => [], 'ledger' => [], 'csrfToken' => Csrf::token(), 'loadError' => 'Finans verileri yuklenemedi.'];
        }
    }

    private function withdrawalsData(): array
    {
        try {
            return [
                'title' => 'ZPanel - Para Cekme Talepleri',
                'section' => 'para-cekme-talepleri',
                'withdrawals' => (new WithdrawalRequest())->listForAdmin((string) ($_GET['status'] ?? '') ?: null, 150),
                'activeStatus' => (string) ($_GET['status'] ?? ''),
                'csrfToken' => Csrf::token(),
            ];
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.withdrawalsData');

            return ['title' => 'ZPanel - Para Cekme Talepleri', 'section' => 'para-cekme-talepleri', 'withdrawals' => [], 'activeStatus' => '', 'csrfToken' => Csrf::token(), 'loadError' => 'Talepler yuklenemedi.'];
        }
    }

    private function siteBankData(): array
    {
        try {
            return [
                'title' => 'ZPanel - Site Banka Hesaplari',
                'section' => 'site-banka-hesaplari',
                'accounts' => (new BankAccount())->listSiteAccounts(false),
                'csrfToken' => Csrf::token(),
            ];
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.siteBankData');

            return ['title' => 'ZPanel - Site Banka Hesaplari', 'section' => 'site-banka-hesaplari', 'accounts' => [], 'csrfToken' => Csrf::token(), 'loadError' => 'Site banka hesaplari yuklenemedi.'];
        }
    }

    private function financialSettingsData(): array
    {
        try {
            return [
                'title' => 'ZPanel - Finans Ayarlari',
                'section' => 'finans-ayarlari',
                'settings' => (new Setting())->getFinancialSettings(),
                'csrfToken' => Csrf::token(),
            ];
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.financialSettingsData');

            return ['title' => 'ZPanel - Finans Ayarlari', 'section' => 'finans-ayarlari', 'settings' => Setting::financialDefaults(), 'csrfToken' => Csrf::token(), 'loadError' => 'Finans ayarlari yuklenemedi.'];
        }
    }

    private function testToolsData(): array
    {
        return [
            'title' => 'ZPanel - Test Araclari',
            'section' => 'test-araclari',
            'csrfToken' => Csrf::token(),
            'featureTestMode' => $this->featureTestMode(),
        ];
    }

    private function createTestMod(PDO $pdo): array
    {
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $categoryId = $this->categoryId($pdo, 'mod');
        $slug = 'test-mod-' . strtolower(bin2hex(random_bytes(3)));
        $statement = $pdo->prepare(
            'INSERT INTO mods
                (user_id, creator_id, category_id, title, slug, summary, description, external_link,
                 external_link_status, external_link_risk_score, cover_path, cover_image, privacy,
                 package_id, file_url, access_type, status, featured, published_at, created_at, updated_at)
             VALUES
                (:user_id, :creator_id, :category_id, :title, :slug, :summary, :description, :external_link,
                 "safe", 5, "/uploads/covers/mod-1.svg", "/uploads/covers/mod-1.svg", "public",
                 NULL, :file_url, "free", "published", 1, NOW(), NOW(), NOW())'
        );
        $statement->execute([
            'user_id' => $creatorId,
            'creator_id' => $creatorId,
            'category_id' => $categoryId,
            'title' => 'Test Mod ' . date('H:i:s'),
            'slug' => $slug,
            'summary' => 'ZPanel test araci ile olusturuldu.',
            'description' => 'Local test modu icin demo mod kaydi.',
            'external_link' => 'https://drive.google.com/',
            'file_url' => 'https://drive.google.com/',
        ]);

        return ['message' => 'Test mod olusturuldu.', 'id' => (int) $pdo->lastInsertId()];
    }

    private function createTestMusic(PDO $pdo, bool $withAudio): array
    {
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $categoryId = $this->categoryId($pdo, 'music');
        $slug = 'test-music-' . strtolower(bin2hex(random_bytes(3)));
        $audio = $withAudio ? '/uploads/music-files/test-demo.mp3' : '';
        $statement = $pdo->prepare(
            'INSERT INTO music_tracks
                (user_id, category_id, title, slug, artist_name, description, cover_image, audio_file,
                 external_link, youtube_url, spotify_url, apple_music_url, privacy, package_id, status,
                 play_count, view_count, like_count, featured, created_at, updated_at)
             VALUES
                (:user_id, :category_id, :title, :slug, "Zalorox Test", "ZPanel test araci ile olusturuldu.",
                 "/uploads/music-covers/track-1.svg", :audio_file, "https://music.youtube.com/",
                 "https://www.youtube.com/watch?v=ScMzIvxBSi4", "https://open.spotify.com/",
                 "https://music.apple.com/", "public", NULL, "published", 0, 0, 0, 1, NOW(), NOW())'
        );
        $statement->execute([
            'user_id' => $creatorId,
            'category_id' => $categoryId,
            'title' => $withAudio ? 'Test Muzik Audio ' . date('H:i:s') : 'Test Muzik Demo ' . date('H:i:s'),
            'slug' => $slug,
            'audio_file' => $audio,
        ]);

        return ['message' => $withAudio ? 'Test muzik kaydi olusturuldu.' : 'Audio dosyasi olmadan demo muzik kaydi olusturuldu.', 'id' => (int) $pdo->lastInsertId()];
    }

    private function createTestPayment(PDO $pdo, string $status): array
    {
        $userId = $this->userId($pdo, 'user') ?: $this->userId($pdo, 'admin');
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $packageId = $this->packageId($pdo, $creatorId);
        $orderNo = 'TEST-' . date('YmdHis') . '-' . strtoupper(bin2hex(random_bytes(2)));
        $orderStatus = $status === 'paid' ? 'completed' : 'failed';
        $amount = 49.90;

        $statement = $pdo->prepare(
            'INSERT INTO orders
                (order_no, user_id, creator_id, package_id, type, period_type, gross_amount, discount_amount,
                 provider_fee_rate, provider_fee_fixed, provider_fee_amount, commission_rate, commission_amount,
                 tax_rate, tax_amount, creator_net_amount, platform_net_amount, subtotal, platform_fee,
                 creator_amount, total, currency, payment_provider, provider_reference, status, created_at, updated_at)
             VALUES
                (:order_no, :user_id, :creator_id, :package_id, "subscription", "monthly", :amount, 0,
                 0, 0, 0, 0.10, 4.99, 0.20, 9.98, 34.93, 4.99, :amount, 4.99,
                 34.93, :amount, "TRY", "test", :provider_reference, :status, NOW(), NOW())'
        );
        $reference = 'test_' . strtolower(bin2hex(random_bytes(5)));
        $statement->execute([
            'order_no' => $orderNo,
            'user_id' => $userId,
            'creator_id' => $creatorId,
            'package_id' => $packageId,
            'amount' => $amount,
            'provider_reference' => $reference,
            'status' => $orderStatus,
        ]);
        $orderId = (int) $pdo->lastInsertId();

        $payment = $pdo->prepare(
            'INSERT INTO payments
                (order_id, provider, provider_reference, provider_payment_id, payment_session_id, status, amount,
                 currency, card_type, card_brand, installment_count, is_3d_secure, raw_request, raw_response,
                 paid_at, failed_at, created_at, updated_at)
             VALUES
                (:order_id, "test", :reference, :reference2, :session_id, :status, :amount,
                 "TRY", "card", "visa", 1, 1, "{}", "{}", :paid_at, :failed_at, NOW(), NOW())'
        );
        $payment->execute([
            'order_id' => $orderId,
            'reference' => $reference,
            'reference2' => $reference,
            'session_id' => 'sess_' . strtolower(bin2hex(random_bytes(4))),
            'status' => $status,
            'amount' => $amount,
            'paid_at' => $status === 'paid' ? date('Y-m-d H:i:s') : null,
            'failed_at' => $status === 'failed' ? date('Y-m-d H:i:s') : null,
        ]);

        return ['message' => $status === 'paid' ? 'Test basarili odeme olusturuldu.' : 'Test basarisiz odeme olusturuldu.', 'order_no' => $orderNo];
    }

    private function createTestSubscription(PDO $pdo): array
    {
        $userId = $this->userId($pdo, 'user') ?: $this->userId($pdo, 'admin');
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $packageId = $this->packageId($pdo, $creatorId);

        $statement = $pdo->prepare(
            'INSERT INTO subscriptions
                (subscriber_id, user_id, creator_id, package_id, order_id, starts_at, ends_at, status, created_at, updated_at)
             VALUES
                (:subscriber_id, :user_id, :creator_id, :package_id, NULL, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), "active", NOW(), NOW())
             ON DUPLICATE KEY UPDATE status = "active", ends_at = DATE_ADD(NOW(), INTERVAL 30 DAY), updated_at = NOW()'
        );
        $statement->execute([
            'subscriber_id' => $userId,
            'user_id' => $userId,
            'creator_id' => $creatorId,
            'package_id' => $packageId,
        ]);

        return ['message' => 'Test abonelik/premium erisim olusturuldu.'];
    }

    private function createTestEarning(PDO $pdo): array
    {
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $pdo->prepare(
            'INSERT INTO creator_ledgers (creator_id, order_id, type, amount, currency, description, available_at, created_at)
             VALUES (:creator_id, NULL, "earning", 125.50, "TRY", "Test creator kazanci", NOW(), NOW())'
        )->execute(['creator_id' => $creatorId]);
        $pdo->prepare(
            'INSERT INTO creator_balances (creator_id, pending_balance, available_balance, withdrawn_balance, currency, updated_at)
             VALUES (:creator_id, 0, 125.50, 0, "TRY", NOW())
             ON DUPLICATE KEY UPDATE available_balance = available_balance + 125.50, updated_at = NOW()'
        )->execute(['creator_id' => $creatorId]);

        return ['message' => 'Test creator kazanci olusturuldu.'];
    }

    private function createTestWithdrawal(PDO $pdo): array
    {
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $accountId = $this->bankAccountId($pdo, $creatorId);
        $pdo->prepare(
            'INSERT INTO withdrawal_requests (creator_id, bank_account_id, amount, currency, status, admin_note, requested_at)
             VALUES (:creator_id, :bank_account_id, 50.00, "TRY", "pending", "Test para cekme talebi", NOW())'
        )->execute(['creator_id' => $creatorId, 'bank_account_id' => $accountId]);

        return ['message' => 'Test para cekme talebi olusturuldu.'];
    }

    private function createTestComment(PDO $pdo): array
    {
        $userId = $this->userId($pdo, 'user') ?: $this->userId($pdo, 'admin');
        $contentId = (int) ($pdo->query('SELECT id FROM mods ORDER BY id DESC LIMIT 1')->fetchColumn() ?: 0);
        if ($contentId <= 0) {
            $this->createTestMod($pdo);
            $contentId = (int) $pdo->lastInsertId();
        }

        $pdo->prepare(
            'INSERT INTO comments
                (content_type, content_id, commentable_type, commentable_id, user_id, parent_id, content, body, status, risk_score, created_at, updated_at)
             VALUES
                ("mod", :content_id, "mod", :content_id2, :user_id, NULL, "Test yorum icerigi", "Test yorum icerigi", "published", 1, NOW(), NOW())'
        )->execute(['content_id' => $contentId, 'content_id2' => $contentId, 'user_id' => $userId]);

        return ['message' => 'Test yorum olusturuldu.'];
    }

    private function createTestMessage(PDO $pdo): array
    {
        $userId = $this->userId($pdo, 'user') ?: $this->userId($pdo, 'admin');
        $creatorId = $this->userId($pdo, 'creator') ?: $this->userId($pdo, 'admin');
        $pdo->prepare('INSERT INTO conversations (type, created_at, updated_at) VALUES ("private", NOW(), NOW())')->execute();
        $conversationId = (int) $pdo->lastInsertId();

        foreach ([$userId, $creatorId] as $participantId) {
            $pdo->prepare(
                'INSERT INTO conversation_participants (conversation_id, user_id, last_read_at, archived_at)
                 VALUES (:conversation_id, :user_id, NULL, NULL)'
            )->execute(['conversation_id' => $conversationId, 'user_id' => $participantId]);
        }

        $pdo->prepare(
            'INSERT INTO messages
                (conversation_id, sender_id, message_type, body, attachment_path, gif_url, link_url, is_deleted, status, risk_score, created_at, updated_at)
             VALUES
                (:conversation_id, :sender_id, "text", "ZPanel test mesaji", NULL, NULL, NULL, 0, "sent", 1, NOW(), NOW())'
        )->execute(['conversation_id' => $conversationId, 'sender_id' => $userId]);

        return ['message' => 'Test mesaj olusturuldu.'];
    }

    private function createTestNotification(PDO $pdo): array
    {
        $userId = $this->userId($pdo, 'user') ?: $this->userId($pdo, 'admin');
        $pdo->prepare(
            'INSERT INTO notifications (user_id, type, title, body, data, read_at, created_at)
             VALUES (:user_id, "test", "Test bildirimi", "ZPanel test araci bildirimi.", "{}", NULL, NOW())'
        )->execute(['user_id' => $userId]);

        return ['message' => 'Test bildirim olusturuldu.'];
    }

    private function runSecurityScan(string $url): array
    {
        $result = (new ZaloroxShield($this->config))->scanLink($url, Auth::id(), 'zpanel_test', null);

        return ['message' => 'Guvenlik taramasi tamamlandi.', 'scan' => $result];
    }

    private function resetTestData(PDO $pdo): array
    {
        $pdo->exec('DELETE FROM mod_images WHERE mod_id IN (SELECT id FROM mods WHERE slug LIKE "test-mod-%")');
        $pdo->exec('DELETE FROM mod_videos WHERE mod_id IN (SELECT id FROM mods WHERE slug LIKE "test-mod-%")');
        $pdo->exec('DELETE FROM mods WHERE slug LIKE "test-mod-%"');
        $pdo->exec('DELETE FROM music_tracks WHERE slug LIKE "test-music-%"');
        $pdo->exec('DELETE FROM notifications WHERE type = "test"');

        return ['message' => 'Test verileri sifirlandi.'];
    }

    private function runSeed(): array
    {
        if (!function_exists('shell_exec')) {
            return ['message' => 'Seed komutu calistirilamadi; shell_exec kapali.', 'todo' => true];
        }

        $binary = defined('PHP_BINARY') ? PHP_BINARY : 'php';
        $command = escapeshellarg($binary) . ' ' . escapeshellarg(BASE_PATH . '/database/seed.php') . ' 2>&1';
        $output = trim((string) shell_exec($command));

        return ['message' => $output !== '' ? $output : 'Seed tekrar yuklendi.'];
    }

    private function userId(PDO $pdo, string $role): int
    {
        $statement = $pdo->prepare('SELECT id FROM users WHERE role = :role AND status = "active" ORDER BY id ASC LIMIT 1');
        $statement->execute(['role' => $role]);

        return (int) ($statement->fetchColumn() ?: 0);
    }

    private function categoryId(PDO $pdo, string $type): ?int
    {
        $statement = $pdo->prepare('SELECT id FROM categories WHERE type = :type AND is_active = 1 ORDER BY parent_id IS NOT NULL DESC, sort_order ASC, id ASC LIMIT 1');
        $statement->execute(['type' => $type]);
        $id = (int) ($statement->fetchColumn() ?: 0);

        return $id > 0 ? $id : null;
    }

    private function packageId(PDO $pdo, int $creatorId): int
    {
        $statement = $pdo->prepare('SELECT id FROM packages WHERE (creator_id = :creator_id OR user_id = :creator_id2) ORDER BY id ASC LIMIT 1');
        $statement->execute(['creator_id' => $creatorId, 'creator_id2' => $creatorId]);
        $packageId = (int) ($statement->fetchColumn() ?: 0);

        if ($packageId > 0) {
            return $packageId;
        }

        $slug = 'test-paket-' . strtolower(bin2hex(random_bytes(3)));
        $pdo->prepare(
            'INSERT INTO packages
                (user_id, creator_id, name, slug, description, price_monthly, price_6_month, price_12_month, price,
                 currency, billing_period, benefits, status, is_active)
             VALUES
                (:creator_id, :creator_id2, "Test Paket", :slug, "Test premium erisim paketi", 49.90, 269.90, 499.90, 49.90,
                 "TRY", "monthly", "[]", "active", 1)'
        )->execute(['creator_id' => $creatorId, 'creator_id2' => $creatorId, 'slug' => $slug]);

        return (int) $pdo->lastInsertId();
    }

    private function bankAccountId(PDO $pdo, int $creatorId): int
    {
        $statement = $pdo->prepare('SELECT id FROM bank_accounts WHERE user_id = :user_id ORDER BY id ASC LIMIT 1');
        $statement->execute(['user_id' => $creatorId]);
        $id = (int) ($statement->fetchColumn() ?: 0);
        if ($id > 0) {
            return $id;
        }

        $pdo->prepare(
            'INSERT INTO bank_accounts (user_id, label, account_holder, iban, bank_name, is_site_account, is_default, created_at, updated_at)
             VALUES (:user_id, "Test IBAN", "Zalorox Creator", "TR000000000000000000000000", "Test Bank", 0, 1, NOW(), NOW())'
        )->execute(['user_id' => $creatorId]);

        return (int) $pdo->lastInsertId();
    }

    private function shieldSettings(): array
    {
        try {
            return (new Setting())->getShieldSettings();
        } catch (Throwable) {
            return Setting::shieldDefaults();
        }
    }

    private function latestLogs(): array
    {
        try {
            return (new SecurityScanLog())->latest(50);
        } catch (Throwable) {
            return [];
        }
    }

    private function listSecurityTable(string $table): array
    {
        try {
            $table = match ($table) {
                'domain_whitelist' => 'domain_whitelist',
                'domain_blacklist' => 'domain_blacklist',
                'forbidden_words' => 'forbidden_words',
                default => throw new \InvalidArgumentException('Invalid table.'),
            };
            $statement = Database::connection($this->config)->query("SELECT * FROM {$table} ORDER BY id DESC LIMIT 100");

            return $statement->fetchAll();
        } catch (Throwable) {
            return [];
        }
    }

    private function jsonPayload(): array
    {
        $decoded = json_decode(file_get_contents('php://input') ?: '', true);

        return is_array($decoded) ? $decoded : [];
    }

    private function featureTestMode(): bool
    {
        return !empty($this->config['FEATURE_TEST_MODE']) && (string) ($this->config['APP_ENV'] ?? '') !== 'production';
    }

    public function usersList(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        try {
            return Response::json(['ok' => true, 'users' => (new User())->adminList($_GET, 200)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.usersList');

            return Response::json(['ok' => false, 'message' => 'Kullanicilar yuklenemedi.'], 500);
        }
    }

    public function updateUserRole(): Response
    {
        return $this->mutateUser('role');
    }

    public function updateUserStatus(): Response
    {
        return $this->mutateUser('status');
    }

    public function modsList(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        try {
            return Response::json(['ok' => true, 'mods' => (new Mod())->adminList($_GET, 200)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.modsList');

            return Response::json(['ok' => false, 'message' => 'Modlar yuklenemedi.'], 500);
        }
    }

    public function approveMod(): Response
    {
        return $this->mutateModStatus('published', 'mod.approve', 'Mod onaylandi.');
    }

    public function rejectMod(): Response
    {
        return $this->mutateModStatus('rejected', 'mod.reject', 'Mod reddedildi.');
    }

    public function unpublishMod(): Response
    {
        return $this->mutateModStatus('archived', 'mod.unpublish', 'Mod yayindan kaldirildi.');
    }

    public function deleteMod(): Response
    {
        return $this->mutateModStatus('archived', 'mod.delete', 'Mod silindi ve arsive alindi.');
    }

    public function featureMod(): Response
    {
        return $this->mutateModFeature(true);
    }

    public function unfeatureMod(): Response
    {
        return $this->mutateModFeature(false);
    }

    public function musicList(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        try {
            return Response::json(['ok' => true, 'tracks' => (new Music())->adminList($_GET, 200)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.musicList');

            return Response::json(['ok' => false, 'message' => 'Muzikler yuklenemedi.'], 500);
        }
    }

    public function approveMusic(): Response
    {
        return $this->mutateMusicStatus('published', 'music.approve', 'Muzik onaylandi.');
    }

    public function rejectMusic(): Response
    {
        return $this->mutateMusicStatus('rejected', 'music.reject', 'Muzik reddedildi.');
    }

    public function unpublishMusic(): Response
    {
        return $this->mutateMusicStatus('archived', 'music.unpublish', 'Muzik yayindan kaldirildi.');
    }

    public function deleteMusic(): Response
    {
        return $this->mutateMusicStatus('archived', 'music.delete', 'Muzik arsive alindi.');
    }

    public function featureMusic(): Response
    {
        return $this->mutateMusicFeature(true);
    }

    public function unfeatureMusic(): Response
    {
        return $this->mutateMusicFeature(false);
    }

    public function reviewMusic(): Response
    {
        return $this->mutateMusicStatus('pending_review', 'music.copyright_review', 'Muzik telif incelemesine alindi.');
    }

    public function mediaList(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        try {
            return Response::json(['ok' => true, 'media' => (new MediaFile())->listForAdmin($_GET, 240)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mediaList');

            return Response::json(['ok' => false, 'message' => 'Medya dosyalari yuklenemedi.'], 500);
        }
    }

    public function uploadMedia(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        try {
            $context = $this->normalizeMediaContext((string) ($payload['context'] ?? 'general'));
            $upload = $this->storeSiteMedia($_FILES['file'] ?? [], $context);
            $id = (new MediaFile())->createFromUpload($upload + [
                'user_id' => Auth::id(),
                'context' => $context,
            ]);
            $upload['id'] = $id;

            if (in_array($context, ['site_logo', 'music_logo', 'studio_logo', 'zpanel_logo', 'favicon'], true)) {
                $this->saveMediaSetting($context, $upload['file_path']);
            }

            (new AdminLog())->write('media.upload', 'media_file', $id, ['context' => $context, 'file_path' => $upload['file_path']]);

            return Response::json(['ok' => true, 'message' => 'Medya dosyasi yuklendi.', 'media' => $upload]);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage(), 'errors' => ['file' => $exception->getMessage()]], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.uploadMedia');

            return Response::json(['ok' => false, 'message' => 'Medya dosyasi yuklenemedi.'], 500);
        }
    }

    public function deleteMedia(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Medya ID gecersiz.', 'errors' => ['id' => 'Medya ID gecersiz.']], 422);
        }

        try {
            (new MediaFile())->deactivate($id);
            (new AdminLog())->write('media.delete', 'media_file', $id, []);

            return Response::json(['ok' => true, 'message' => 'Medya dosyasi pasife alindi.']);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.deleteMedia');

            return Response::json(['ok' => false, 'message' => 'Medya dosyasi silinemedi.'], 500);
        }
    }

    public function setLogoMedia(): Response
    {
        return $this->setMediaSettingFromPayload('logo');
    }

    public function setFaviconMedia(): Response
    {
        return $this->setMediaSettingFromPayload('favicon');
    }

    public function approveArtist(): Response
    {
        return $this->mutateArtist('approved');
    }

    public function rejectArtist(): Response
    {
        return $this->mutateArtist('rejected');
    }

    public function approveCopyright(): Response
    {
        return $this->mutateCopyright('clean', 'music_copyright.approve', 'Telif kontrolu temiz olarak isaretlendi.');
    }

    public function rejectCopyright(): Response
    {
        return $this->mutateCopyright('blocked', 'music_copyright.reject', 'Muzik telif nedeniyle engellendi.', true);
    }

    public function blockCopyright(): Response
    {
        return $this->mutateCopyright('blocked', 'music_copyright.block', 'Muzik blocklandi.', true);
    }

    public function createCategory(): Response
    {
        return $this->mutateCategory('create');
    }

    public function updateCategory(): Response
    {
        return $this->mutateCategory('update');
    }

    public function deleteCategory(): Response
    {
        return $this->mutateCategory('delete');
    }

    public function createPage(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $errors = $this->validatePagePayload($payload);
        if ($errors !== []) {
            return Response::json(['ok' => false, 'message' => 'Sayfa alanlarini kontrol et.', 'errors' => $errors], 422);
        }

        try {
            $id = (new Page())->createFromAdmin($payload, (int) Auth::id());
            if (($payload['language'] ?? 'tr') === 'tr') {
                try {
                    (new ZaloroxTranslateBot($this->config))->createDrafts(
                        'page',
                        $id,
                        (string) ($payload['title'] ?? ''),
                        (string) ($payload['seo_description'] ?? ''),
                        (string) ($payload['content'] ?? ''),
                        (int) Auth::id()
                    );
                } catch (Throwable $translationException) {
                    $this->logException($translationException, 'zpanel.translate.page.create');
                }
            }
            (new AdminLog())->write('page.create', 'page', $id, ['slug' => $payload['slug'] ?? '', 'language' => $payload['language'] ?? 'tr']);

            return Response::json(['ok' => true, 'id' => $id, 'message' => 'Sayfa olusturuldu.', 'redirect' => url('/zpanel/sayfa-duzenle/' . $id)]);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.createPage');

            return Response::json(['ok' => false, 'message' => 'Sayfa olusturulamadi.'], 500);
        }
    }

    public function updatePage(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Sayfa ID gecersiz.'], 422);
        }

        $errors = $this->validatePagePayload($payload);
        if ($errors !== []) {
            return Response::json(['ok' => false, 'message' => 'Sayfa alanlarini kontrol et.', 'errors' => $errors], 422);
        }

        try {
            (new Page())->updateFromAdmin($id, $payload, (int) Auth::id());
            if (($payload['language'] ?? 'tr') === 'tr') {
                try {
                    (new ZaloroxTranslateBot($this->config))->createDrafts(
                        'page',
                        $id,
                        (string) ($payload['title'] ?? ''),
                        (string) ($payload['seo_description'] ?? ''),
                        (string) ($payload['content'] ?? ''),
                        (int) Auth::id()
                    );
                } catch (Throwable $translationException) {
                    $this->logException($translationException, 'zpanel.translate.page.update');
                }
            }
            (new AdminLog())->write('page.update', 'page', $id, ['slug' => $payload['slug'] ?? '', 'language' => $payload['language'] ?? 'tr']);

            return Response::json(['ok' => true, 'message' => 'Sayfa kaydedildi.']);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.updatePage');

            return Response::json(['ok' => false, 'message' => 'Sayfa kaydedilemedi.'], 500);
        }
    }

    public function trashPage(): Response
    {
        return $this->setPageStatus('trashed', 'page.trash', 'Sayfa cope tasindi.');
    }

    public function deletePage(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Sayfa ID gecersiz.'], 422);
        }

        try {
            $pageModel = new Page();
            $page = $pageModel->findForAdmin($id);
            if (!$page) {
                return Response::json(['ok' => false, 'message' => 'Sayfa bulunamadi.'], 404);
            }

            $deleted = $pageModel->deletePermanent($id);
            if (!$deleted) {
                return Response::json(['ok' => false, 'message' => 'Sayfa silinemedi.'], 422);
            }

            (new AdminLog())->write('page.delete', 'page', $id, [
                'slug' => $page['slug'] ?? '',
                'language' => $page['language'] ?? 'tr',
                'title' => $page['title'] ?? '',
            ]);

            return Response::json(['ok' => true, 'message' => 'Sayfa kalici olarak silindi.', 'redirect' => url('/zpanel/sayfalar')]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.deletePage');

            return Response::json(['ok' => false, 'message' => 'Sayfa silinemedi.'], 500);
        }
    }

    public function publishPage(): Response
    {
        return $this->setPageStatus('published', 'page.publish', 'Sayfa yayina alindi.');
    }

    public function hidePage(): Response
    {
        return $this->setPageStatus('hidden', 'page.hide', 'Sayfa gizlendi.');
    }

    public function restorePage(): Response
    {
        return $this->setPageStatus('draft', 'page.restore', 'Sayfa taslaga geri alindi.');
    }

    public function commentsList(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        try {
            return Response::json(['ok' => true, 'comments' => $this->commentsRows($_GET, 200)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.commentsList');

            return Response::json(['ok' => false, 'message' => 'Yorumlar yuklenemedi.'], 500);
        }
    }

    public function approveComment(): Response
    {
        return $this->setCommentStatus('published', 'comment.approve', 'Yorum onaylandi.');
    }

    public function blockComment(): Response
    {
        return $this->setCommentStatus('blocked', 'comment.block', 'Yorum engellendi.');
    }

    public function deleteComment(): Response
    {
        return $this->setCommentStatus('deleted', 'comment.delete', 'Yorum silindi.');
    }

    public function sendNotification(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        try {
            $count = (new Notification())->sendFromAdmin($payload);
            (new AdminLog())->write('notification.send', 'notification', null, ['target_type' => $payload['target_type'] ?? 'all', 'count' => $count]);

            return Response::json(['ok' => true, 'message' => $count . ' kullaniciya bildirim gonderildi.', 'count' => $count]);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.sendNotification');

            return Response::json(['ok' => false, 'message' => 'Bildirim gonderilemedi.'], 500);
        }
    }

    public function updateSettings(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $errors = $this->validateSiteSettings($payload);
        if ($errors !== []) {
            return Response::json(['ok' => false, 'message' => 'Lutfen zorunlu alanlari kontrol et.', 'errors' => $errors], 422);
        }

        try {
            $settings = (new Setting())->saveSiteSettings($payload);
            (new AdminLog())->write('settings.update', 'settings', null, ['keys' => array_keys($settings)]);

            return Response::json(['ok' => true, 'message' => 'Site ayarlari kaydedildi.', 'settings' => $settings]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.updateSettings');

            return Response::json(['ok' => false, 'message' => 'Site ayarlari kaydedilemedi.'], 500);
        }
    }

    public function adminLogsList(): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        try {
            return Response::json(['ok' => true, 'logs' => (new AdminLog())->listForAdmin($_GET, 200)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.adminLogsList');

            return Response::json(['ok' => false, 'message' => 'Admin loglari yuklenemedi.'], 500);
        }
    }

    private function requireAdminPage(): void
    {
        if (!Auth::check()) {
            header('Location: ' . url('/giris-yap'));
            exit;
        }

        if (!Auth::role('admin')) {
            http_response_code(403);
            echo $this->view('main', 'pages/show', [
                'title' => '403',
                'page' => [
                    'title' => 'Yetkisiz erisim',
                    'content' => 'Bu alani goruntulemek icin admin yetkisi gerekir.',
                ],
            ]);
            exit;
        }
    }

    private function dashboardData(): array
    {
        return [
            'title' => 'ZPanel Dashboard',
            'section' => 'dashboard',
            'csrfToken' => Csrf::token(),
            'testMode' => $this->featureTestMode(),
            'stats' => [
                'total_users' => $this->safeScalar('SELECT COUNT(*) FROM users'),
                'total_mods' => $this->safeScalar('SELECT COUNT(*) FROM mods'),
                'total_music' => $this->safeScalar('SELECT COUNT(*) FROM music_tracks'),
                'total_payments' => $this->safeScalar('SELECT COUNT(*) FROM payments'),
                'total_earnings' => $this->safeScalar('SELECT COALESCE(SUM(creator_net_amount), 0) FROM orders WHERE status IN ("paid", "completed")'),
                'pending_mods' => $this->safeScalar('SELECT COUNT(*) FROM mods WHERE status = "pending"'),
                'pending_music' => $this->safeScalar('SELECT COUNT(*) FROM music_tracks WHERE status = "pending"'),
                'pending_withdrawals' => $this->safeScalar('SELECT COUNT(*) FROM withdrawal_requests WHERE status = "pending"'),
                'security_alerts' => $this->safeScalar('SELECT COUNT(*) FROM security_scan_logs WHERE risk_score >= 61'),
            ],
            'adminLogs' => $this->safeList(fn (): array => (new AdminLog())->listForAdmin([], 8)),
            'securityLogs' => $this->latestLogs(),
            'payments' => $this->queryRows(
                'SELECT o.order_no, o.total, o.currency, o.status, o.created_at, u.username, p.provider
                 FROM orders o
                 LEFT JOIN users u ON u.id = o.user_id
                 LEFT JOIN payments p ON p.order_id = o.id
                 ORDER BY o.created_at DESC
                 LIMIT 8'
            ),
            'users' => $this->queryRows(
                'SELECT id, username, email, role, status, created_at
                 FROM users
                 ORDER BY created_at DESC
                 LIMIT 8'
            ),
            'latestMods' => $this->queryRows(
                'SELECT id, title, slug, status, created_at
                 FROM mods
                 ORDER BY created_at DESC
                 LIMIT 5'
            ),
            'latestMusic' => $this->queryRows(
                'SELECT id, title, slug, status, artist_name, created_at
                 FROM music_tracks
                 ORDER BY created_at DESC
                 LIMIT 5'
            ),
        ];
    }

    private function usersData(): array
    {
        return [
            'title' => 'ZPanel - Kullanicilar',
            'section' => 'kullanicilar',
            'users' => $this->safeList(fn (): array => (new User())->adminList($_GET, 120)),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function modsData(): array
    {
        $groups = $this->safeList(fn (): array => (new Category())->adminGroups());

        return [
            'title' => 'ZPanel - Modlar',
            'section' => 'modlar',
            'mods' => $this->safeList(fn (): array => (new Mod())->adminList($_GET, 120)),
            'categories' => $groups['mod'] ?? [],
            'csrfToken' => Csrf::token(),
        ];
    }

    private function musicData(): array
    {
        return [
            'title' => 'ZPanel - Muzikler',
            'section' => 'muzikler',
            'tracks' => $this->safeList(fn (): array => (new Music())->adminList($_GET, 120)),
            'genres' => $this->safeList(fn (): array => (new Music())->genres()),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function artistsData(): array
    {
        return [
            'title' => 'ZPanel - Artist Basvurulari',
            'section' => 'artist-basvurulari',
            'artists' => $this->safeList(fn (): array => (new Artist())->listForAdmin($_GET, 140)),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function musicCopyrightData(): array
    {
        return [
            'title' => 'ZPanel - Muzik Telif Inceleme',
            'section' => 'muzik-telif',
            'checks' => $this->queryRows(
                'SELECT mcc.*, mt.slug, mt.status AS track_status, u.username
                 FROM music_copyright_checks mcc
                 LEFT JOIN music_tracks mt ON mt.id = mcc.track_id
                 LEFT JOIN users u ON u.id = mcc.user_id
                 WHERE (:status = "" OR mcc.status = :status_filter)
                 ORDER BY FIELD(mcc.status, "pending", "review", "duplicate", "blocked", "clean"), mcc.created_at DESC
                 LIMIT 160',
                ['status' => (string) ($_GET['status'] ?? ''), 'status_filter' => (string) ($_GET['status'] ?? '')]
            ),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function categoriesData(string $section = 'kategoriler'): array
    {
        $activeGroup = match ($section) {
            'mod-kategorileri' => 'mod',
            'muzik-turleri' => 'music_genre',
            default => (string) ($_GET['group'] ?? 'mod'),
        };

        return [
            'title' => 'ZPanel - Kategoriler',
            'section' => $section,
            'activeGroup' => $activeGroup,
            'groups' => $this->safeList(fn (): array => (new Category())->adminGroups()),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function packagesData(): array
    {
        return [
            'title' => 'ZPanel - Paketler / Abonelikler',
            'section' => 'abonelikler',
            'packages' => $this->queryRows(
                'SELECT p.*, u.username AS creator_username,
                        COALESCE(up.display_name, u.nickname, u.username) AS creator_name,
                        COUNT(CASE WHEN s.status = "active" THEN 1 END) AS active_subscribers
                 FROM packages p
                 LEFT JOIN users u ON u.id = COALESCE(p.user_id, p.creator_id)
                 LEFT JOIN user_profiles up ON up.user_id = u.id
                 LEFT JOIN subscriptions s ON s.package_id = p.id
                 GROUP BY p.id
                 ORDER BY p.created_at DESC
                 LIMIT 120'
            ),
            'subscriptions' => $this->queryRows(
                'SELECT s.*, sub.username AS subscriber_username, cr.username AS creator_username, p.name AS package_name
                 FROM subscriptions s
                 LEFT JOIN users sub ON sub.id = COALESCE(s.subscriber_id, s.user_id)
                 LEFT JOIN users cr ON cr.id = s.creator_id
                 LEFT JOIN packages p ON p.id = s.package_id
                 ORDER BY s.created_at DESC
                 LIMIT 120'
            ),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function paymentsData(): array
    {
        return [
            'title' => 'ZPanel - Odemeler',
            'section' => 'odemeler',
            'payments' => $this->queryRows(
                'SELECT o.order_no, o.total, o.gross_amount, o.currency, o.status AS order_status, o.created_at,
                        u.username AS user_username, c.username AS creator_username, pkg.name AS package_name,
                        p.provider, p.status AS payment_status, p.provider_reference, p.paid_at
                 FROM orders o
                 LEFT JOIN payments p ON p.order_id = o.id
                 LEFT JOIN users u ON u.id = o.user_id
                 LEFT JOIN users c ON c.id = o.creator_id
                 LEFT JOIN packages pkg ON pkg.id = o.package_id
                 ORDER BY o.created_at DESC
                 LIMIT 160'
            ),
        ];
    }

    private function pagesData(): array
    {
        return [
            'title' => 'ZPanel - Sayfa Yonetimi',
            'section' => 'sayfalar',
            'pages' => $this->safeList(fn (): array => (new Page())->listForAdmin($_GET, 140)),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function pageFormData(int $id = 0): array
    {
        $page = null;
        $loadError = null;

        if ($id > 0) {
            try {
                $page = (new Page())->findForAdmin($id);
                if (!$page) {
                    $loadError = 'Sayfa bulunamadi.';
                }
            } catch (Throwable) {
                $loadError = 'Sayfa yuklenemedi.';
            }
        }

        return [
            'title' => $id > 0 ? 'ZPanel - Sayfa Duzenle' : 'ZPanel - Sayfa Ekle',
            'section' => $id > 0 ? 'sayfa-duzenle' : 'sayfa-ekle',
            'page' => $page,
            'loadError' => $loadError,
            'csrfToken' => Csrf::token(),
        ];
    }

    private function commentsData(): array
    {
        return [
            'title' => 'ZPanel - Yorum Yonetimi',
            'section' => 'yorumlar',
            'comments' => $this->commentsRows($_GET, 160),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function communityData(): array
    {
        return [
            'title' => 'ZPanel - Topluluk Yonetimi',
            'section' => 'community',
            'summary' => $this->safeList(fn (): array => (new CommunityPost())->summary()),
            'posts' => $this->safeList(fn (): array => (new CommunityPost())->listForAdmin($_GET, 160)),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function messageReportsData(): array
    {
        return [
            'title' => 'ZPanel - Mesaj Raporlari',
            'section' => 'mesaj-raporlari',
            'reports' => $this->queryRows(
                'SELECT mr.*, reporter.username AS reporter_username, owner.username AS owner_username,
                        m.body AS message_body, m.status AS message_status, m.created_at AS message_created_at
                 FROM message_reports mr
                 LEFT JOIN messages m ON m.id = mr.message_id
                 LEFT JOIN users reporter ON reporter.id = mr.reporter_id
                 LEFT JOIN users owner ON owner.id = m.sender_id
                 ORDER BY FIELD(mr.status, "open", "reviewed", "actioned", "dismissed"), mr.created_at DESC
                 LIMIT 160'
            ),
        ];
    }

    private function socialRecordsData(): array
    {
        return [
            'title' => 'ZPanel - Sosyal Kayitlar',
            'section' => 'sosyal-kayitlar',
            'friendships' => $this->queryRows(
                'SELECT f.*, requester.username AS requester_username, receiver.username AS receiver_username
                 FROM friendships f
                 LEFT JOIN users requester ON requester.id = f.requester_id
                 LEFT JOIN users receiver ON receiver.id = f.receiver_id
                 ORDER BY f.created_at DESC
                 LIMIT 160'
            ),
            'blocks' => $this->queryRows(
                'SELECT b.*, blocker.username AS blocker_username, blocked.username AS blocked_username
                 FROM blocked_users b
                 LEFT JOIN users blocker ON blocker.id = b.blocker_id
                 LEFT JOIN users blocked ON blocked.id = b.blocked_id
                 ORDER BY b.created_at DESC
                 LIMIT 160'
            ),
        ];
    }

    private function notificationsData(): array
    {
        return [
            'title' => 'ZPanel - Bildirimler',
            'section' => 'bildirimler',
            'recent' => $this->queryRows(
                'SELECT n.*, u.username
                 FROM notifications n
                 LEFT JOIN users u ON u.id = n.user_id
                 ORDER BY n.created_at DESC
                 LIMIT 80'
            ),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function settingsData(string $section = 'settings'): array
    {
        return [
            'title' => 'ZPanel - Site Ayarlari',
            'section' => $section,
            'settings' => $this->safeList(fn (): array => (new Setting())->getSiteSettings()) ?: Setting::siteDefaults(),
            'csrfToken' => Csrf::token(),
        ];
    }

    private function mediaData(): array
    {
        $settings = $this->safeList(fn (): array => (new Setting())->getSiteSettings()) ?: Setting::siteDefaults();

        return [
            'title' => 'ZPanel - Medya / Logo Yonetimi',
            'section' => 'media',
            'settings' => $settings,
            'mediaFiles' => $this->safeList(fn (): array => (new MediaFile())->listForAdmin($_GET, 200)),
            'csrfToken' => Csrf::token(),
            'uploadDir' => '/uploads/site-media/',
        ];
    }

    private function fileManagerData(): array
    {
        return [
            'title' => 'ZPanel - Dosya Yoneticisi',
            'section' => 'dosya-yoneticisi',
            'currentDir' => $this->safeUploadRelativePath((string) ($_GET['dir'] ?? '')),
            'items' => $this->uploadItems((string) ($_GET['dir'] ?? '')),
        ];
    }

    private function reportsData(): array
    {
        return [
            'title' => 'ZPanel - Raporlar',
            'section' => 'raporlar',
            'stats' => [
                'users_30d' => $this->safeScalar('SELECT COUNT(*) FROM users WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)'),
                'mods' => $this->safeScalar('SELECT COUNT(*) FROM mods'),
                'music' => $this->safeScalar('SELECT COUNT(*) FROM music_tracks'),
                'payments' => $this->safeScalar('SELECT COUNT(*) FROM payments'),
                'creator_net' => $this->safeScalar('SELECT COALESCE(SUM(creator_net_amount), 0) FROM orders WHERE status IN ("paid", "completed")'),
            ],
            'topMods' => $this->queryRows('SELECT title, slug, download_count, view_count FROM mods ORDER BY download_count DESC, view_count DESC LIMIT 10'),
            'topMusic' => $this->queryRows('SELECT title, slug, artist_name, play_count, view_count FROM music_tracks ORDER BY play_count DESC, view_count DESC LIMIT 10'),
            'riskLogs' => $this->queryRows('SELECT * FROM security_scan_logs ORDER BY risk_score DESC, created_at DESC LIMIT 10'),
        ];
    }

    private function logsData(): array
    {
        return [
            'title' => 'ZPanel - Admin Loglari',
            'section' => 'loglar',
            'logs' => $this->safeList(fn (): array => (new AdminLog())->listForAdmin($_GET, 160)),
        ];
    }

    private function mutateUser(string $field): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['user_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Kullanici ID gecersiz.'], 422);
        }

        try {
            $model = new User();
            if ($field === 'role') {
                $value = (string) ($payload['role'] ?? 'user');
                $model->updateRoleByAdmin($id, $value, (int) Auth::id());
                (new AdminLog())->write('user.role.update', 'user', $id, ['role' => $value]);

                return Response::json(['ok' => true, 'message' => 'Kullanici rolu guncellendi.']);
            }

            $value = (string) ($payload['status'] ?? 'active');
            $model->updateStatusByAdmin($id, $value, (int) Auth::id());
            (new AdminLog())->write('user.status.update', 'user', $id, ['status' => $value]);

            return Response::json(['ok' => true, 'message' => 'Kullanici durumu guncellendi.']);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mutateUser');

            return Response::json(['ok' => false, 'message' => 'Kullanici guncellenemedi.'], 500);
        }
    }

    private function mutateModStatus(string $status, string $action, string $message): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['mod_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Mod ID gecersiz.'], 422);
        }

        try {
            (new Mod())->updateStatusByAdmin($id, $status);
            (new AdminLog())->write($action, 'mod', $id, ['status' => $status, 'reason' => $payload['reason'] ?? null]);

            return Response::json(['ok' => true, 'message' => $message]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mutateModStatus');

            return Response::json(['ok' => false, 'message' => 'Mod guncellenemedi.'], 500);
        }
    }

    private function mutateModFeature(bool $featured): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['mod_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Mod ID gecersiz.'], 422);
        }

        try {
            (new Mod())->setFeaturedByAdmin($id, $featured);
            (new AdminLog())->write($featured ? 'mod.feature' : 'mod.unfeature', 'mod', $id, ['featured' => $featured]);

            return Response::json(['ok' => true, 'message' => $featured ? 'Mod one cikarildi.' : 'Mod one cikarmadan alindi.']);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mutateModFeature');

            return Response::json(['ok' => false, 'message' => 'Mod guncellenemedi.'], 500);
        }
    }

    private function mutateMusicStatus(string $status, string $action, string $message): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['music_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Muzik ID gecersiz.'], 422);
        }

        try {
            (new Music())->updateStatusByAdmin($id, $status);
            (new AdminLog())->write($action, 'music', $id, ['status' => $status, 'reason' => $payload['reason'] ?? null]);

            return Response::json(['ok' => true, 'message' => $message]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mutateMusicStatus');

            return Response::json(['ok' => false, 'message' => 'Muzik guncellenemedi.'], 500);
        }
    }

    private function mutateMusicFeature(bool $featured): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['music_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Muzik ID gecersiz.'], 422);
        }

        try {
            (new Music())->setFeaturedByAdmin($id, $featured);
            (new AdminLog())->write($featured ? 'music.feature' : 'music.unfeature', 'music', $id, ['featured' => $featured]);

            return Response::json(['ok' => true, 'message' => $featured ? 'Muzik one cikarildi.' : 'Muzik one cikarmadan alindi.']);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mutateMusicFeature');

            return Response::json(['ok' => false, 'message' => 'Muzik guncellenemedi.'], 500);
        }
    }

    private function mutateArtist(string $status): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['artist_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Artist ID gecersiz.'], 422);
        }

        try {
            $model = new Artist();
            if ($status === 'approved') {
                $model->approve($id);
                (new AdminLog())->write('artist.approve', 'artist', $id, []);

                return Response::json(['ok' => true, 'message' => 'Artist basvurusu onaylandi.']);
            }

            $reason = (string) ($payload['reason'] ?? $payload['rejection_reason'] ?? '');
            $model->reject($id, $reason);
            (new AdminLog())->write('artist.reject', 'artist', $id, ['reason' => $reason]);

            return Response::json(['ok' => true, 'message' => 'Artist basvurusu reddedildi.']);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.artist.mutate');

            return Response::json(['ok' => false, 'message' => 'Artist basvurusu guncellenemedi.'], 500);
        }
    }

    private function mutateCopyright(string $status, string $action, string $message, bool $blockTrack = false): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['check_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Telif kontrol ID gecersiz.'], 422);
        }

        try {
            $pdo = Database::connection($this->config);
            $statement = $pdo->prepare('SELECT track_id FROM music_copyright_checks WHERE id = :id LIMIT 1');
            $statement->execute(['id' => $id]);
            $trackId = (int) ($statement->fetchColumn() ?: 0);
            $pdo->prepare('UPDATE music_copyright_checks SET status = :status WHERE id = :id')
                ->execute(['status' => $status, 'id' => $id]);

            if ($trackId > 0) {
                $nextStatus = $blockTrack ? 'blocked' : 'published';
                $pdo->prepare('UPDATE music_tracks SET status = :status, updated_at = NOW() WHERE id = :id')
                    ->execute(['status' => $nextStatus, 'id' => $trackId]);
            }

            (new AdminLog())->write($action, 'music_copyright_check', $id, ['status' => $status, 'track_id' => $trackId]);

            return Response::json(['ok' => true, 'message' => $message]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.copyright.mutate');

            return Response::json(['ok' => false, 'message' => 'Telif kontrolu guncellenemedi.'], 500);
        }
    }

    private function mutateCategory(string $mode): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        if ($mode !== 'delete' && trim((string) ($payload['name'] ?? '')) === '') {
            return Response::json(['ok' => false, 'message' => 'Kategori adi zorunludur.', 'errors' => ['name' => 'Kategori adi zorunludur.']], 422);
        }

        try {
            if ($mode !== 'delete') {
                $payload = $this->applyCategoryUploads($payload);
            }
            $model = new Category();
            $id = (int) ($payload['id'] ?? 0);

            if ($mode === 'delete') {
                if ($id < 1) {
                    return Response::json(['ok' => false, 'message' => 'Kategori ID gecersiz.'], 422);
                }
                $model->deleteFromAdmin($id, (string) ($payload['group'] ?? 'mod'));
                (new AdminLog())->write('category.delete', 'category', $id, ['group' => $payload['group'] ?? 'mod']);

                return Response::json(['ok' => true, 'message' => 'Kategori pasife alindi.']);
            }

            if ($mode === 'update') {
                if ($id < 1) {
                    return Response::json(['ok' => false, 'message' => 'Kategori ID gecersiz.'], 422);
                }
                $model->updateFromAdmin($id, $payload);
                (new AdminLog())->write('category.update', 'category', $id, ['group' => $payload['group'] ?? $payload['type'] ?? 'mod']);

                return Response::json(['ok' => true, 'message' => 'Kategori guncellendi.']);
            }

            $newId = $model->createFromAdmin($payload);
            (new AdminLog())->write('category.create', 'category', $newId, ['group' => $payload['group'] ?? $payload['type'] ?? 'mod']);

            return Response::json(['ok' => true, 'message' => 'Kategori olusturuldu.', 'id' => $newId]);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.mutateCategory');

            return Response::json(['ok' => false, 'message' => 'Kategori islemi tamamlanamadi.'], 500);
        }
    }

    private function applyCategoryUploads(array $payload): array
    {
        foreach (['icon_file' => 'icon', 'banner_file' => 'banner'] as $fileKey => $targetKey) {
            $file = $_FILES[$fileKey] ?? null;
            if (!is_array($file) || ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
                continue;
            }

            $context = $targetKey === 'icon' ? 'category_icon' : 'category_banner';
            $upload = $this->storeSiteMedia($file, $context);
            $mediaId = (new MediaFile())->createFromUpload($upload + [
                'user_id' => Auth::id(),
            ]);
            $payload[$targetKey] = $upload['file_path'];
            (new AdminLog())->write('category.media_upload', 'media_file', $mediaId, [
                'field' => $targetKey,
                'file_path' => $upload['file_path'],
            ]);
        }

        return $payload;
    }

    private function setPageStatus(string $status, string $action, string $message): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Sayfa ID gecersiz.'], 422);
        }

        try {
            (new Page())->setStatus($id, $status, (int) Auth::id());
            (new AdminLog())->write($action, 'page', $id, ['status' => $status]);

            return Response::json(['ok' => true, 'message' => $message]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.setPageStatus');

            return Response::json(['ok' => false, 'message' => 'Sayfa durumu guncellenemedi.'], 500);
        }
    }

    private function setCommentStatus(string $status, string $action, string $message): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $id = (int) ($payload['id'] ?? $payload['comment_id'] ?? 0);
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Yorum ID gecersiz.'], 422);
        }

        try {
            if ($status === 'deleted') {
                (new Comment())->softDelete($id);
            } else {
                Database::connection($this->config)->prepare('UPDATE comments SET status = :status, updated_at = NOW() WHERE id = :id')->execute(['status' => $status, 'id' => $id]);
            }
            (new AdminLog())->write($action, 'comment', $id, ['status' => $status]);

            return Response::json(['ok' => true, 'message' => $message]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.setCommentStatus');

            return Response::json(['ok' => false, 'message' => 'Yorum guncellenemedi.'], 500);
        }
    }

    private function commentsRows(array $filters, int $limit): array
    {
        $limit = min(max($limit, 1), 300);
        $where = [];
        $params = [];

        if (!empty($filters['content_type'])) {
            $where[] = 'COALESCE(cm.content_type, cm.commentable_type) = :content_type';
            $params['content_type'] = (string) $filters['content_type'];
        }

        if (!empty($filters['status'])) {
            $where[] = 'cm.status = :status';
            $params['status'] = (string) $filters['status'];
        }

        if (!empty($filters['q'])) {
            $where[] = '(cm.content LIKE :q_content OR cm.body LIKE :q_body OR u.username LIKE :q_user)';
            $like = '%' . trim((string) $filters['q']) . '%';
            $params['q_content'] = $like;
            $params['q_body'] = $like;
            $params['q_user'] = $like;
        }

        $sql = 'SELECT cm.id,
                       COALESCE(cm.content_type, cm.commentable_type) AS content_type,
                       COALESCE(cm.content_id, cm.commentable_id) AS content_id,
                       COALESCE(cm.content, cm.body) AS content,
                       cm.status,
                       cm.risk_score,
                       cm.created_at,
                       u.username,
                       COALESCE(up.display_name, u.nickname, u.username) AS display_name
                FROM comments cm
                LEFT JOIN users u ON u.id = cm.user_id
                LEFT JOIN user_profiles up ON up.user_id = u.id';
        if ($where !== []) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }
        $sql .= ' ORDER BY cm.created_at DESC LIMIT :limit';

        $statement = Database::connection($this->config)->prepare($sql);
        foreach ($params as $key => $value) {
            $statement->bindValue($key, $value);
        }
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return $statement->fetchAll();
    }

    private function queryRows(string $sql, array $params = []): array
    {
        try {
            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);

            return $statement->fetchAll();
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.queryRows');

            return [];
        }
    }

    private function safeScalar(string $sql, array $params = []): float|int
    {
        try {
            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);
            $value = $statement->fetchColumn();

            return is_numeric($value) ? (float) $value : 0;
        } catch (Throwable) {
            return 0;
        }
    }

    private function safeList(callable $callback): array
    {
        try {
            return $callback();
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.safeList');

            return [];
        }
    }

    private function uploadItems(string $relativeDir): array
    {
        $relative = $this->safeUploadRelativePath($relativeDir);
        $base = realpath(BASE_PATH . '/public/uploads');
        if ($base === false) {
            return [];
        }

        $target = realpath($base . ($relative !== '' ? DIRECTORY_SEPARATOR . $relative : ''));
        if ($target === false) {
            return [];
        }

        $baseWithSeparator = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $targetWithSeparator = rtrim($target, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        if ($target !== $base && !str_starts_with($targetWithSeparator, $baseWithSeparator)) {
            return [];
        }

        $items = [];
        foreach (scandir($target) ?: [] as $name) {
            if ($name === '.' || $name === '..') {
                continue;
            }

            $path = $target . DIRECTORY_SEPARATOR . $name;
            $isDir = is_dir($path);
            $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
            $items[] = [
                'name' => $name,
                'relative' => trim($relative . '/' . $name, '/'),
                'type' => $isDir ? 'folder' : ($extension ?: 'file'),
                'size' => $isDir ? null : filesize($path),
                'modified_at' => date('Y-m-d H:i:s', filemtime($path) ?: time()),
                'view_url' => !$isDir && $extension !== 'php' ? '/uploads/' . trim($relative . '/' . $name, '/') : null,
                'is_php' => $extension === 'php',
            ];
        }

        usort($items, static fn (array $a, array $b): int => strcmp($a['type'] . $a['name'], $b['type'] . $b['name']));

        return $items;
    }

    private function safeUploadRelativePath(string $value): string
    {
        $value = trim(str_replace('\\', '/', $value), '/');
        $parts = array_filter(explode('/', $value), static fn (string $part): bool => $part !== '' && $part !== '.' && $part !== '..');

        return implode('/', array_map(static fn (string $part): string => preg_replace('/[^A-Za-z0-9._-]/', '', $part) ?: '', $parts));
    }

    private function normalizeMediaContext(string $context): string
    {
        return in_array($context, ['site_logo', 'music_logo', 'studio_logo', 'zpanel_logo', 'favicon', 'general'], true)
            ? $context
            : 'general';
    }

    private function storeSiteMedia(array $file, string $context): array
    {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            throw new \InvalidArgumentException('Dosya secilmedi.');
        }

        if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
            throw new \InvalidArgumentException('Dosya yuklenemedi.');
        }

        $tmp = (string) ($file['tmp_name'] ?? '');
        if ($tmp === '' || !is_file($tmp)) {
            throw new \InvalidArgumentException('Gecersiz gecici dosya.');
        }

        $size = (int) ($file['size'] ?? 0);
        if ($size <= 0 || $size > 8 * 1024 * 1024) {
            throw new \InvalidArgumentException('Dosya boyutu maksimum 8 MB olabilir.');
        }

        $original = (string) ($file['name'] ?? 'upload');
        $extension = strtolower(pathinfo($original, PATHINFO_EXTENSION));
        $dangerous = ['php', 'phtml', 'phar', 'js', 'html', 'htm', 'exe', 'bat', 'cmd', 'sh'];
        $allowed = ['jpg', 'jpeg', 'png', 'webp', 'gif'];
        if (in_array($extension, $dangerous, true) || !in_array($extension, $allowed, true)) {
            throw new \InvalidArgumentException('Sadece jpg, jpeg, png, webp ve gif dosyalari kabul edilir.');
        }

        $mime = mime_content_type($tmp) ?: '';
        if (!in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true)) {
            throw new \InvalidArgumentException('Dosya tipi guvenli degil.');
        }

        $dir = BASE_PATH . '/public/uploads/site-media';
        if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
            throw new \RuntimeException('Upload klasoru olusturulamadi.');
        }

        $baseName = strtolower(pathinfo($original, PATHINFO_FILENAME));
        $baseName = preg_replace('/[^a-z0-9]+/', '-', $baseName) ?: $context;
        $baseName = trim($baseName, '-') ?: $context;
        $fileName = substr($baseName, 0, 70) . '-' . date('YmdHis') . '-' . bin2hex(random_bytes(4)) . '.' . $extension;
        $target = $dir . DIRECTORY_SEPARATOR . $fileName;

        if (!move_uploaded_file($tmp, $target) && !rename($tmp, $target)) {
            throw new \RuntimeException('Dosya kaydedilemedi.');
        }

        return [
            'file_path' => '/uploads/site-media/' . $fileName,
            'file_name' => $fileName,
            'mime_type' => $mime,
            'file_size' => $size,
            'file_type' => $context === 'favicon' ? 'favicon' : 'image',
            'context' => $context,
        ];
    }

    private function setMediaSettingFromPayload(string $mode): Response
    {
        if ($response = $this->requireAdminJson()) {
            return $response;
        }

        $payload = $this->payload();
        if (!$this->validCsrf($payload)) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        try {
            $key = $mode === 'favicon'
                ? 'favicon'
                : $this->normalizeMediaContext((string) ($payload['target'] ?? 'site_logo'));
            if ($mode !== 'favicon' && !in_array($key, ['site_logo', 'music_logo', 'studio_logo', 'zpanel_logo'], true)) {
                return Response::json(['ok' => false, 'message' => 'Logo hedefi gecersiz.', 'errors' => ['target' => 'Logo hedefi gecersiz.']], 422);
            }

            $path = $this->mediaPathFromPayload($payload);
            $this->saveMediaSetting($key, $path);
            (new AdminLog())->write($mode === 'favicon' ? 'media.set_favicon' : 'media.set_logo', 'settings', null, ['key' => $key, 'file_path' => $path]);

            return Response::json(['ok' => true, 'message' => $mode === 'favicon' ? 'Favicon kaydedildi.' : 'Logo kaydedildi.', 'key' => $key, 'file_path' => $path]);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage(), 'errors' => ['file_path' => $exception->getMessage()]], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'zpanel.setMediaSetting');

            return Response::json(['ok' => false, 'message' => 'Medya ayari kaydedilemedi.'], 500);
        }
    }

    private function mediaPathFromPayload(array $payload): string
    {
        $id = (int) ($payload['id'] ?? $payload['media_id'] ?? 0);
        if ($id > 0) {
            $media = (new MediaFile())->find($id);
            if (!$media) {
                throw new \InvalidArgumentException('Medya dosyasi bulunamadi.');
            }

            return $this->assertPublicMediaPath((string) ($media['file_path'] ?? ''));
        }

        return $this->assertPublicMediaPath((string) ($payload['file_path'] ?? ''));
    }

    private function assertPublicMediaPath(string $path): string
    {
        $path = '/' . ltrim(trim(str_replace('\\', '/', $path)), '/');
        if (!str_starts_with($path, '/uploads/site-media/') && !str_starts_with($path, '/assets/img/')) {
            throw new \InvalidArgumentException('Medya yolu gecersiz.');
        }

        $publicRoot = realpath(BASE_PATH . '/public');
        $absolute = realpath(BASE_PATH . '/public' . $path);
        if ($publicRoot === false || $absolute === false) {
            throw new \InvalidArgumentException('Medya dosyasi bulunamadi.');
        }

        $publicRoot = rtrim(str_replace('\\', '/', $publicRoot), '/') . '/';
        $absolute = str_replace('\\', '/', $absolute);
        if (!str_starts_with($absolute, $publicRoot)) {
            throw new \InvalidArgumentException('Medya yolu gecersiz.');
        }

        return $path;
    }

    private function saveMediaSetting(string $key, string $path): void
    {
        $setting = new Setting();
        $public = $key === 'zpanel_logo' ? 0 : 1;
        $setting->set($key, $path, 'string', $public);

        if ($key === 'site_logo') {
            $setting->set('logo_path', $path, 'string', 1);
        }

        if ($key === 'zpanel_logo') {
            $setting->set('zpanel_logo_path', $path, 'string', 0);
        }
    }

    private function validateSiteSettings(array $payload): array
    {
        $errors = [];
        if (trim((string) ($payload['site_name'] ?? '')) === '') {
            $errors['site_name'] = 'Site adi zorunludur.';
        }

        if (trim((string) ($payload['site_description'] ?? '')) === '') {
            $errors['site_description'] = 'Site aciklamasi zorunludur.';
        }

        $language = substr(preg_replace('/[^a-z]/', '', strtolower((string) ($payload['default_language'] ?? 'tr'))) ?: 'tr', 0, 2);
        if (!in_array($language, ['tr', 'en', 'de', 'fr'], true)) {
            $errors['default_language'] = 'Desteklenen bir dil sec.';
        }

        if (!in_array((string) ($payload['default_theme'] ?? 'system'), ['dark', 'light', 'system'], true)) {
            $errors['default_theme'] = 'Tema dark, light veya system olmali.';
        }

        return $errors;
    }

    private function validatePagePayload(array $payload): array
    {
        $errors = [];
        if (trim((string) ($payload['title'] ?? '')) === '') {
            $errors['title'] = 'Sayfa basligi zorunludur.';
        }
        if (trim((string) ($payload['slug'] ?? '')) === '') {
            $errors['slug'] = 'Slug zorunludur.';
        }
        if (trim((string) ($payload['content'] ?? '')) === '') {
            $errors['content'] = 'Icerik zorunludur.';
        }
        if (!in_array((string) ($payload['status'] ?? 'draft'), Page::STATUSES, true)) {
            $errors['status'] = 'Gecerli bir durum sec.';
        }
        if (isset($payload['sort_order']) && !is_numeric($payload['sort_order'])) {
            $errors['sort_order'] = 'Siralama sayisal olmalidir.';
        }

        return $errors;
    }

    private function requireAdminJson(): ?Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Bu islem icin giris yapmalisin.'], 401);
        }

        if (!Auth::role('admin')) {
            return Response::json(['ok' => false, 'message' => 'Bu islem icin admin yetkisi gerekir.'], 403);
        }

        return null;
    }

    private function validCsrf(array $payload): bool
    {
        return Csrf::verify((string) ($payload['_csrf'] ?? ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? '')));
    }

    private function payload(): array
    {
        $payload = $_POST;
        $raw = file_get_contents('php://input') ?: '';
        if ($raw !== '') {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                $payload = array_merge($payload, $decoded);
            } elseif ($payload === []) {
                parse_str($raw, $parsed);
                if (is_array($parsed)) {
                    $payload = $parsed;
                }
            }
        }

        if (empty($payload['_csrf']) && !empty($_SERVER['HTTP_X_CSRF_TOKEN'])) {
            $payload['_csrf'] = (string) $_SERVER['HTTP_X_CSRF_TOKEN'];
        }

        return $payload;
    }

    private function zpanelSegments(): array
    {
        $path = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/');
        $segments = $path === '' ? [] : explode('/', $path);
        if (($segments[0] ?? '') === 'zpanel') {
            array_shift($segments);
        }

        return $segments;
    }
}
