<?php

declare(strict_types=1);

namespace App\Http\Controllers\Admin;

use App\Domain\Sysapi\Contracts\SysapiBridge;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Inertia\Inertia;
use Inertia\Response;

/**
 * ModSecurity / WAF — Gelişmiş Yönetim.
 *
 * 9 sekme: Dashboard, Ayarlar, Kurallar, İstisnalar, Audit Log,
 *          IP Whitelist, Özel Kurallar, CRS Update, Test Aracı.
 *
 * Tüm veri kaynakları:
 *   - DB: modsecurity_settings, modsecurity_rule_exclusions, modsecurity_whitelist,
 *         modsecurity_custom_rules, modsecurity_audit_events, modsecurity_crs_updates
 *   - sysapi: modsec-status, modsec-rule-write, modsec-enable, modsec-disable,
 *             modsec-rules-list, modsec-audit-search, modsec-test-request,
 *             modsec-whitelist-write, modsec-custom-rule-write, modsec-crs-update
 *
 * Mock data ÜRETMEZ.
 */
class ModSecurityController extends Controller
{
    private const RULESETS = ['OWASP-CRS-3.3', 'OWASP-CRS-4.0', 'Comodo-CWAF', 'Custom'];
    private const SEVERITIES = ['CRITICAL', 'ERROR', 'WARNING', 'NOTICE', 'INFO', 'DEBUG'];
    private const ENGINE_MODES = ['on', 'detection_only', 'off'];

    public function __construct(private readonly SysapiBridge $sysapi) {}

    /**
     * Ana sayfa — 9 sekme için tüm veriyi tek call'da hazırlar.
     */
    public function index(Request $request): Response
    {
        // ── Settings (tüm anahtarlar) ──────────────────────────────────────
        $settings = DB::table('modsecurity_settings')->pluck('value', 'key')->toArray();

        // ── Exclusions ─────────────────────────────────────────────────────
        $exclusions = DB::table('modsecurity_rule_exclusions')
            ->leftJoin('users as u', 'u.id', '=', 'modsecurity_rule_exclusions.created_by')
            ->leftJoin('accounts as a', 'a.id', '=', 'modsecurity_rule_exclusions.account_id')
            ->select(
                'modsecurity_rule_exclusions.*',
                'u.name as created_by_name',
                'a.username as account_username'
            )
            ->latest('modsecurity_rule_exclusions.id')
            ->limit(200)
            ->get();

        // ── Whitelist ───────────────────────────────────────────────────────
        $whitelist = Schema::hasTable('modsecurity_whitelist')
            ? DB::table('modsecurity_whitelist')
                ->leftJoin('users as u', 'u.id', '=', 'modsecurity_whitelist.created_by')
                ->select('modsecurity_whitelist.*', 'u.name as created_by_name')
                ->latest()
                ->limit(200)
                ->get()
            : collect([]);

        // ── Custom rules ───────────────────────────────────────────────────
        $custom_rules = Schema::hasTable('modsecurity_custom_rules')
            ? DB::table('modsecurity_custom_rules')
                ->leftJoin('users as u', 'u.id', '=', 'modsecurity_custom_rules.created_by')
                ->select('modsecurity_custom_rules.*', 'u.name as created_by_name')
                ->latest()
                ->get()
            : collect([]);

        // ── Audit events (recent 100, paginated when filtering) ───────────
        $audit_events = Schema::hasTable('modsecurity_audit_events')
            ? DB::table('modsecurity_audit_events')
                ->orderByDesc('event_at')
                ->limit(100)
                ->get()
            : collect([]);

        // ── CRS update history ─────────────────────────────────────────────
        $crs_history = Schema::hasTable('modsecurity_crs_updates')
            ? DB::table('modsecurity_crs_updates')
                ->orderByDesc('created_at')
                ->limit(10)
                ->get()
            : collect([]);

        // ── Dashboard stats (sysapi modsec-status) ─────────────────────────
        $stats = [
            'blocks_24h' => 0,
            'blocks_7d' => 0,
            'blocks_30d' => 0,
            'unique_ips_24h' => 0,
            'top_rules' => [],
            'top_ips' => [],
            'top_uris' => [],
            'top_categories' => [],
            'hourly_chart' => [],
        ];
        try {
            $result = $this->sysapi->run('modsec-status', ['window_h' => 24], timeoutSeconds: 10);
            if ($result->successful) {
                $stats['blocks_24h'] = $result->data['blocks_24h'] ?? 0;
                $stats['top_rules'] = $result->data['top_rules'] ?? [];
                $stats['top_ips'] = $result->data['top_blocked_ips'] ?? [];
                $stats['top_uris'] = $result->data['top_uris'] ?? [];
                $stats['top_categories'] = $result->data['top_categories'] ?? [];
                $stats['hourly_chart'] = $result->data['hourly'] ?? [];
            }
        } catch (\Throwable $e) {
            Log::warning('modsec.status_failed', ['error' => $e->getMessage()]);
        }

        // ── DB-based stats (audit_events tablosundan) ──────────────────────
        if (Schema::hasTable('modsecurity_audit_events')) {
            $stats['blocks_7d'] = DB::table('modsecurity_audit_events')
                ->where('event_at', '>=', now()->subDays(7))
                ->where('action_taken', 'blocked')
                ->count();
            $stats['blocks_30d'] = DB::table('modsecurity_audit_events')
                ->where('event_at', '>=', now()->subDays(30))
                ->where('action_taken', 'blocked')
                ->count();
            $stats['unique_ips_24h'] = DB::table('modsecurity_audit_events')
                ->where('event_at', '>=', now()->subDay())
                ->distinct('source_ip')
                ->count('source_ip');
        }

        // ── Available rules (CRS rule katalog) ─────────────────────────────
        $rules_catalog = [];
        try {
            $result = $this->sysapi->run('modsec-rules-list', [], timeoutSeconds: 10);
            if ($result->successful) {
                $rules_catalog = $result->data['rules'] ?? [];
            }
        } catch (\Throwable $e) {
            // soft-fail
        }

        // ── CRS available version check ────────────────────────────────────
        $crs_info = [
            'installed' => $settings['crs_version'] ?? 'unknown',
            'latest' => null,
            'update_available' => false,
        ];
        try {
            $result = $this->sysapi->run('modsec-crs-update', ['check_only' => true], timeoutSeconds: 15);
            if ($result->successful) {
                $crs_info['latest'] = $result->data['latest_version'] ?? null;
                $crs_info['update_available'] = (bool) ($result->data['update_available'] ?? false);
            }
        } catch (\Throwable $e) {
            // soft-fail
        }

        return Inertia::render('Admin/ModSecurity/Index', [
            'settings' => $settings,
            'exclusions' => $exclusions,
            'whitelist' => $whitelist,
            'custom_rules' => $custom_rules,
            'audit_events' => $audit_events,
            'crs_history' => $crs_history,
            'rulesets' => self::RULESETS,
            'severities' => self::SEVERITIES,
            'engine_modes' => self::ENGINE_MODES,
            'stats' => $stats,
            'rules_catalog' => $rules_catalog,
            'crs_info' => $crs_info,
        ]);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Settings
    // ═══════════════════════════════════════════════════════════════════════

    public function updateSettings(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'engine_mode' => ['required', 'in:'.implode(',', self::ENGINE_MODES)],
            'audit_log_enabled' => ['required', 'boolean'],
            'audit_log_parts' => ['nullable', 'string', 'max:20', 'regex:/^[A-Z]+$/'],
            'active_ruleset' => ['required', 'string', 'in:'.implode(',', self::RULESETS)],
            'paranoia_level' => ['required', 'integer', 'min:1', 'max:4'],
            'request_body_access' => ['required', 'boolean'],
            'response_body_access' => ['required', 'boolean'],
            'request_body_limit' => ['required', 'integer', 'min:65536', 'max:104857600'],
            'request_body_nofiles_limit' => ['required', 'integer', 'min:8192', 'max:1048576'],
            'default_action' => ['required', 'in:deny,drop,block,redirect'],
            'anomaly_threshold_inbound' => ['required', 'integer', 'min:1', 'max:1000'],
            'anomaly_threshold_outbound' => ['required', 'integer', 'min:1', 'max:1000'],
            'auto_exclude_wordpress' => ['required', 'boolean'],
            'auto_exclude_drupal' => ['required', 'boolean'],
            'auto_exclude_joomla' => ['required', 'boolean'],
        ]);

        // Backward-compat: engine_enabled boolean
        $validated['engine_enabled'] = $validated['engine_mode'] !== 'off' ? '1' : '0';

        foreach ($validated as $k => $v) {
            DB::table('modsecurity_settings')->updateOrInsert(
                ['key' => $k],
                ['value' => is_bool($v) ? ($v ? '1' : '0') : (string) $v, 'updated_at' => now()]
            );
        }

        // Apply to real Apache config via sysapi
        try {
            $action = $validated['engine_mode'] === 'off' ? 'modsec-disable' : 'modsec-enable';
            $payload = [
                'mode' => $validated['engine_mode'],
                'ruleset' => $validated['active_ruleset'],
                'paranoia_level' => $validated['paranoia_level'],
                'audit_log_enabled' => (bool) $validated['audit_log_enabled'],
                'audit_log_parts' => $validated['audit_log_parts'] ?? 'ABIJDEFHZ',
                'request_body_access' => (bool) $validated['request_body_access'],
                'response_body_access' => (bool) $validated['response_body_access'],
                'request_body_limit' => $validated['request_body_limit'],
                'request_body_nofiles_limit' => $validated['request_body_nofiles_limit'],
                'default_action' => $validated['default_action'],
                'anomaly_threshold_inbound' => $validated['anomaly_threshold_inbound'],
                'anomaly_threshold_outbound' => $validated['anomaly_threshold_outbound'],
                'auto_exclude_wordpress' => (bool) $validated['auto_exclude_wordpress'],
                'auto_exclude_drupal' => (bool) $validated['auto_exclude_drupal'],
                'auto_exclude_joomla' => (bool) $validated['auto_exclude_joomla'],
            ];
            $result = $this->sysapi->run($action, $payload, timeoutSeconds: 30);
            if ($result->failed()) {
                return back()->with('warning', 'Ayarlar DB\'ye yazıldı ancak Apache config\'e uygulanamadı: '.$result->stderr);
            }
        } catch (\Throwable $e) {
            Log::error('modsec.apply_sysapi_error', ['error' => $e->getMessage()]);
            return back()->with('warning', 'Ayarlar DB\'ye yazıldı ancak sysapi hatası: '.$e->getMessage());
        }

        return back()->with('success', 'ModSecurity ayarları güncellendi ve Apache reload edildi.');
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Rule Exclusions
    // ═══════════════════════════════════════════════════════════════════════

    public function storeExclusion(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'rule_id' => ['required', 'string', 'regex:/^\d{6}$/'],
            'domain_name' => ['nullable', 'string', 'max:253', 'regex:/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'],
            'reason' => ['nullable', 'string', 'max:500'],
        ]);

        $id = DB::table('modsecurity_rule_exclusions')->insertGetId([
            'account_id' => null,
            'domain_name' => $validated['domain_name'] ?? null,
            'rule_id' => $validated['rule_id'],
            'reason' => $validated['reason'] ?? null,
            'created_by' => auth()->id(),
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        try {
            $result = $this->sysapi->run('modsec-rule-write', [
                'rule_id' => $validated['rule_id'],
                'domain_name' => $validated['domain_name'] ?? null,
                'action' => 'exclude',
            ], timeoutSeconds: 15);

            if ($result->failed()) {
                Log::warning('modsec.rule_write_failed', ['rule_id' => $validated['rule_id'], 'stderr' => $result->stderr]);
                return back()->with('warning', 'İstisna DB\'ye yazıldı ancak Apache conf güncellenemedi: '.$result->stderr);
            }
        } catch (\Throwable $e) {
            Log::error('modsec.rule_write_error', ['error' => $e->getMessage()]);
        }

        return back()->with('success', 'Kural istisnası eklendi ve Apache reload edildi.');
    }

    public function destroyExclusion(int $id): RedirectResponse
    {
        $row = DB::table('modsecurity_rule_exclusions')->where('id', $id)->first();
        if (! $row) {
            return back()->with('error', 'İstisna bulunamadı.');
        }

        DB::table('modsecurity_rule_exclusions')->where('id', $id)->delete();

        try {
            $this->sysapi->run('modsec-rule-write', [
                'rule_id' => $row->rule_id,
                'domain_name' => $row->domain_name,
                'action' => 'remove_exclusion',
            ], timeoutSeconds: 15);
        } catch (\Throwable $e) {
            Log::warning('modsec.exclusion_remove_failed', ['id' => $id, 'error' => $e->getMessage()]);
        }

        return back()->with('success', 'İstisna kaldırıldı.');
    }

    /**
     * Audit log'daki bir rule_id için tek tıkla istisna oluştur.
     */
    public function quickExcludeFromAudit(Request $request, int $auditEventId): RedirectResponse
    {
        $event = DB::table('modsecurity_audit_events')->where('id', $auditEventId)->first();
        if (! $event) {
            return back()->with('error', 'Audit event bulunamadı.');
        }

        $request->merge([
            'rule_id' => $event->rule_id,
            'domain_name' => $event->domain_name,
            'reason' => 'Audit log #'.$auditEventId.' false positive auto-exclusion',
        ]);

        return $this->storeExclusion($request);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // IP Whitelist (bypass ModSecurity for trusted IPs)
    // ═══════════════════════════════════════════════════════════════════════

    public function storeWhitelist(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'ip_or_cidr' => ['required', 'string', 'max:64'],
            'domain_name' => ['nullable', 'string', 'max:253', 'regex:/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'],
            'label' => ['nullable', 'string', 'max:100'],
            'reason' => ['nullable', 'string', 'max:500'],
        ]);

        // IPv4/IPv6/CIDR validation
        $value = $validated['ip_or_cidr'];
        $isV4 = (bool) preg_match('/^(\d{1,3}\.){3}\d{1,3}(\/(\d|[12]\d|3[0-2]))?$/', $value);
        $isV6 = str_contains($value, ':')
            && filter_var(explode('/', $value)[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
        if (! $isV4 && ! $isV6) {
            return back()->withErrors(['ip_or_cidr' => 'Geçersiz IP/CIDR formatı.']);
        }

        DB::table('modsecurity_whitelist')->updateOrInsert(
            ['ip_or_cidr' => $value, 'domain_name' => $validated['domain_name'] ?? null],
            [
                'label' => $validated['label'] ?? null,
                'reason' => $validated['reason'] ?? null,
                'is_active' => true,
                'created_by' => auth()->id(),
                'created_at' => now(),
                'updated_at' => now(),
            ]
        );

        // Apply to Apache config
        $allWhitelisted = DB::table('modsecurity_whitelist')
            ->where('is_active', true)
            ->get(['ip_or_cidr', 'domain_name'])
            ->toArray();

        try {
            $this->sysapi->run('modsec-whitelist-write', [
                'ips' => $allWhitelisted,
            ], timeoutSeconds: 15);
        } catch (\Throwable $e) {
            return back()->with('warning', 'Whitelist DB\'ye yazıldı, Apache reload başarısız: '.$e->getMessage());
        }

        return back()->with('success', "IP whitelist'e eklendi: {$value}");
    }

    public function destroyWhitelist(int $id): RedirectResponse
    {
        DB::table('modsecurity_whitelist')->where('id', $id)->delete();

        $allWhitelisted = DB::table('modsecurity_whitelist')
            ->where('is_active', true)
            ->get(['ip_or_cidr', 'domain_name'])
            ->toArray();

        try {
            $this->sysapi->run('modsec-whitelist-write', [
                'ips' => $allWhitelisted,
            ], timeoutSeconds: 15);
        } catch (\Throwable $e) {
            Log::warning('modsec.whitelist_remove_failed', ['id' => $id, 'error' => $e->getMessage()]);
        }

        return back()->with('success', 'IP whitelist\'ten kaldırıldı.');
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Custom Rules (raw SecRule editor)
    // ═══════════════════════════════════════════════════════════════════════

    public function storeCustomRule(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'description' => ['nullable', 'string', 'max:500'],
            'rule_content' => ['required', 'string', 'max:10000'],
            'category' => ['nullable', 'string', 'max:50'],
            'domain_name' => ['nullable', 'string', 'max:253'],
            'is_enabled' => ['required', 'boolean'],
        ]);

        // Basit SecRule syntax sanity check
        if (! preg_match('/^\s*Sec(Rule|Action|MarkerEnd)/im', $validated['rule_content'])) {
            return back()->withErrors(['rule_content' => 'Geçerli SecRule/SecAction direktifi bulunamadı.']);
        }

        $id = DB::table('modsecurity_custom_rules')->insertGetId([
            'name' => $validated['name'],
            'description' => $validated['description'] ?? null,
            'rule_content' => $validated['rule_content'],
            'category' => $validated['category'] ?? 'custom',
            'is_enabled' => $validated['is_enabled'],
            'domain_name' => $validated['domain_name'] ?? null,
            'created_by' => auth()->id(),
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        // Apache config'e yaz
        try {
            $result = $this->sysapi->run('modsec-custom-rule-write', [
                'rule_id' => $id,
                'name' => $validated['name'],
                'rule_content' => $validated['rule_content'],
                'is_enabled' => $validated['is_enabled'],
                'domain_name' => $validated['domain_name'] ?? null,
            ], timeoutSeconds: 15);

            if ($result->failed()) {
                return back()->with('warning', 'Kural DB\'ye yazıldı, syntax test başarısız: '.$result->stderr);
            }
        } catch (\Throwable $e) {
            Log::warning('modsec.custom_rule_write_failed', ['id' => $id, 'error' => $e->getMessage()]);
        }

        return back()->with('success', "Özel kural eklendi: {$validated['name']}");
    }

    public function toggleCustomRule(int $id): RedirectResponse
    {
        $row = DB::table('modsecurity_custom_rules')->where('id', $id)->first();
        if (! $row) {
            return back()->with('error', 'Özel kural bulunamadı.');
        }

        $newState = ! (bool) $row->is_enabled;
        DB::table('modsecurity_custom_rules')->where('id', $id)->update([
            'is_enabled' => $newState,
            'updated_at' => now(),
        ]);

        try {
            $this->sysapi->run('modsec-custom-rule-write', [
                'rule_id' => $id,
                'name' => $row->name,
                'rule_content' => $row->rule_content,
                'is_enabled' => $newState,
                'domain_name' => $row->domain_name,
            ], timeoutSeconds: 15);
        } catch (\Throwable $e) {
            Log::warning('modsec.custom_rule_toggle_failed', ['id' => $id, 'error' => $e->getMessage()]);
        }

        $verb = $newState ? 'etkinleştirildi' : 'devre dışı bırakıldı';
        return back()->with('success', "'{$row->name}' {$verb}.");
    }

    public function destroyCustomRule(int $id): RedirectResponse
    {
        $row = DB::table('modsecurity_custom_rules')->where('id', $id)->first();
        if (! $row) {
            return back()->with('error', 'Özel kural bulunamadı.');
        }

        DB::table('modsecurity_custom_rules')->where('id', $id)->delete();

        try {
            $this->sysapi->run('modsec-custom-rule-write', [
                'rule_id' => $id,
                'action' => 'delete',
            ], timeoutSeconds: 15);
        } catch (\Throwable $e) {
            Log::warning('modsec.custom_rule_delete_failed', ['id' => $id, 'error' => $e->getMessage()]);
        }

        return back()->with('success', 'Özel kural silindi.');
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Audit Log
    // ═══════════════════════════════════════════════════════════════════════

    public function searchAuditLog(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'q' => ['nullable', 'string', 'max:255'],
            'rule_id' => ['nullable', 'string', 'max:12'],
            'source_ip' => ['nullable', 'string', 'max:45'],
            'domain' => ['nullable', 'string', 'max:253'],
            'severity' => ['nullable', 'in:'.implode(',', self::SEVERITIES)],
            'from' => ['nullable', 'date'],
            'to' => ['nullable', 'date'],
            'page' => ['nullable', 'integer', 'min:1'],
        ]);

        $query = DB::table('modsecurity_audit_events');

        if (! empty($validated['q'])) {
            $q = '%'.$validated['q'].'%';
            $query->where(function ($w) use ($q) {
                $w->where('uri', 'LIKE', $q)
                  ->orWhere('reason', 'LIKE', $q)
                  ->orWhere('rule_tag', 'LIKE', $q);
            });
        }
        if (! empty($validated['rule_id'])) {
            $query->where('rule_id', $validated['rule_id']);
        }
        if (! empty($validated['source_ip'])) {
            $query->where('source_ip', $validated['source_ip']);
        }
        if (! empty($validated['domain'])) {
            $query->where('domain_name', $validated['domain']);
        }
        if (! empty($validated['severity'])) {
            $query->where('severity', $validated['severity']);
        }
        if (! empty($validated['from'])) {
            $query->where('event_at', '>=', $validated['from']);
        }
        if (! empty($validated['to'])) {
            $query->where('event_at', '<=', $validated['to']);
        }

        $total = $query->count();
        $results = $query->orderByDesc('event_at')
            ->offset((($validated['page'] ?? 1) - 1) * 50)
            ->limit(50)
            ->get();

        return response()->json([
            'success' => true,
            'total' => $total,
            'page' => $validated['page'] ?? 1,
            'per_page' => 50,
            'results' => $results,
        ]);
    }

    public function refreshAuditLog(): JsonResponse
    {
        try {
            $result = $this->sysapi->run('modsec-audit-search', [
                'limit' => 500,
                'since_hours' => 24,
            ], timeoutSeconds: 30);

            if ($result->failed()) {
                return response()->json(['success' => false, 'error' => $result->stderr], 500);
            }

            $events = $result->data['events'] ?? [];
            $inserted = 0;
            foreach ($events as $ev) {
                if (! Schema::hasTable('modsecurity_audit_events')) {
                    break;
                }
                $existing = DB::table('modsecurity_audit_events')
                    ->where('event_id', $ev['event_id'] ?? '')
                    ->exists();
                if ($existing) {
                    continue;
                }

                DB::table('modsecurity_audit_events')->insert([
                    'event_id' => $ev['event_id'] ?? bin2hex(random_bytes(8)),
                    'source_ip' => $ev['source_ip'] ?? '0.0.0.0',
                    'country_code' => $ev['country_code'] ?? null,
                    'domain_name' => $ev['domain_name'] ?? null,
                    'uri' => $ev['uri'] ?? '/',
                    'method' => $ev['method'] ?? 'GET',
                    'rule_id' => $ev['rule_id'] ?? '000000',
                    'rule_tag' => $ev['rule_tag'] ?? null,
                    'severity' => $ev['severity'] ?? null,
                    'reason' => $ev['reason'] ?? null,
                    'action_taken' => $ev['action_taken'] ?? 'blocked',
                    'response_code' => $ev['response_code'] ?? null,
                    'matched_data' => isset($ev['matched_data']) ? json_encode($ev['matched_data']) : null,
                    'event_at' => $ev['event_at'] ?? now(),
                    'created_at' => now(),
                ]);
                $inserted++;
            }

            return response()->json([
                'success' => true,
                'inserted' => $inserted,
                'total_fetched' => count($events),
            ]);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 500);
        }
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Test Request (test a sample URL against ruleset)
    // ═══════════════════════════════════════════════════════════════════════

    public function testRequest(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'method' => ['required', 'in:GET,POST,PUT,PATCH,DELETE'],
            'uri' => ['required', 'string', 'max:2000'],
            'headers' => ['nullable', 'string', 'max:4000'],
            'body' => ['nullable', 'string', 'max:50000'],
        ]);

        try {
            $result = $this->sysapi->run('modsec-test-request', $validated, timeoutSeconds: 15);

            if ($result->failed()) {
                return response()->json(['success' => false, 'error' => $result->stderr], 500);
            }

            return response()->json([
                'success' => true,
                'matched_rules' => $result->data['matched_rules'] ?? [],
                'anomaly_score' => $result->data['anomaly_score'] ?? 0,
                'action' => $result->data['action'] ?? 'allow',
                'would_block' => $result->data['would_block'] ?? false,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 500);
        }
    }

    // ═══════════════════════════════════════════════════════════════════════
    // CRS Update
    // ═══════════════════════════════════════════════════════════════════════

    public function updateCrs(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'version' => ['nullable', 'string', 'regex:/^\d+\.\d+\.\d+$/'],
        ]);

        try {
            $result = $this->sysapi->run('modsec-crs-update', [
                'version' => $validated['version'] ?? 'latest',
                'check_only' => false,
            ], timeoutSeconds: 300);   // CRS download is slow

            if ($result->failed()) {
                DB::table('modsecurity_crs_updates')->insert([
                    'from_version' => DB::table('modsecurity_settings')->where('key', 'crs_version')->value('value'),
                    'to_version' => $validated['version'] ?? 'latest',
                    'status' => 'failed',
                    'error_message' => $result->stderr,
                    'triggered_by' => auth()->id(),
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);

                return response()->json(['success' => false, 'error' => $result->stderr], 500);
            }

            $newVersion = $result->data['installed_version'] ?? $validated['version'] ?? 'unknown';
            $oldVersion = DB::table('modsecurity_settings')->where('key', 'crs_version')->value('value');

            DB::table('modsecurity_settings')->updateOrInsert(
                ['key' => 'crs_version'],
                ['value' => $newVersion, 'updated_at' => now()]
            );

            DB::table('modsecurity_crs_updates')->insert([
                'from_version' => $oldVersion,
                'to_version' => $newVersion,
                'status' => 'success',
                'changelog' => $result->data['changelog'] ?? null,
                'triggered_by' => auth()->id(),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            return response()->json([
                'success' => true,
                'from_version' => $oldVersion,
                'to_version' => $newVersion,
                'rules_count' => $result->data['rules_count'] ?? null,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 500);
        }
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Rule Detail + Per-Rule Toggle
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Belirli rule_id'nin tam SecRule source kodunu ve hit history'sini döner.
     */
    public function ruleDetail(string $ruleId): JsonResponse
    {
        if (! preg_match('/^\d{6}$/', $ruleId)) {
            return response()->json(['success' => false, 'error' => 'invalid rule_id'], 422);
        }

        // sysapi'den source code çek
        $detail = ['rule_id' => $ruleId, 'source' => null, 'category' => null, 'severity' => null];
        try {
            $result = $this->sysapi->run('modsec-rule-detail', ['rule_id' => $ruleId], timeoutSeconds: 5);
            if ($result->successful) {
                $detail = array_merge($detail, $result->data);
            }
        } catch (\Throwable $e) {
            // soft-fail
        }

        // DB'den hit history (son 30 gün)
        $hit_history = Schema::hasTable('modsecurity_audit_events')
            ? DB::table('modsecurity_audit_events')
                ->where('rule_id', $ruleId)
                ->where('event_at', '>=', now()->subDays(30))
                ->selectRaw('DATE(event_at) as day, COUNT(*) as count')
                ->groupBy('day')
                ->orderBy('day')
                ->get()
            : collect([]);

        // Mevcut exclusion var mı?
        $is_excluded_globally = DB::table('modsecurity_rule_exclusions')
            ->where('rule_id', $ruleId)
            ->whereNull('domain_name')
            ->exists();

        $domain_exclusions = DB::table('modsecurity_rule_exclusions')
            ->where('rule_id', $ruleId)
            ->whereNotNull('domain_name')
            ->pluck('domain_name')
            ->toArray();

        // State (disabled flag)
        $state = Schema::hasTable('modsecurity_rule_states')
            ? DB::table('modsecurity_rule_states')->where('rule_id', $ruleId)->first()
            : null;

        return response()->json([
            'success' => true,
            'rule_id' => $ruleId,
            'source' => $detail['source'] ?? null,
            'category' => $detail['category'] ?? null,
            'severity' => $detail['severity'] ?? null,
            'tag' => $detail['tag'] ?? null,
            'description' => $detail['description'] ?? null,
            'phase' => $detail['phase'] ?? null,
            'file_path' => $detail['file_path'] ?? null,
            'hit_history_30d' => $hit_history,
            'total_hits_30d' => $hit_history->sum('count'),
            'is_excluded_globally' => $is_excluded_globally,
            'domain_exclusions' => $domain_exclusions,
            'is_disabled' => $state ? ! (bool) $state->is_enabled : false,
        ]);
    }

    /**
     * Per-rule enable/disable toggle.
     */
    public function toggleRule(Request $request, string $ruleId): RedirectResponse
    {
        if (! preg_match('/^\d{6}$/', $ruleId)) {
            return back()->with('error', 'Geçersiz rule_id.');
        }

        $request->validate([
            'reason' => ['nullable', 'string', 'max:500'],
        ]);

        $existing = DB::table('modsecurity_rule_states')->where('rule_id', $ruleId)->first();
        $newState = $existing ? ! (bool) $existing->is_enabled : false; // İlk toggle = disable

        DB::table('modsecurity_rule_states')->updateOrInsert(
            ['rule_id' => $ruleId],
            [
                'is_enabled' => $newState,
                'disabled_reason' => $newState ? null : ($request->input('reason') ?? 'Admin toggle'),
                'changed_by' => auth()->id(),
                'updated_at' => now(),
                'created_at' => $existing ? $existing->created_at : now(),
            ],
        );

        try {
            $this->sysapi->run('modsec-rule-toggle', [
                'rule_id' => $ruleId,
                'enabled' => $newState,
            ], timeoutSeconds: 15);
        } catch (\Throwable $e) {
            Log::warning('modsec.rule_toggle_failed', ['rule_id' => $ruleId, 'error' => $e->getMessage()]);
            return back()->with('warning', "Rule {$ruleId} DB'de güncellendi ancak Apache'ye yansıtılamadı.");
        }

        $verb = $newState ? 'etkinleştirildi' : 'devre dışı bırakıldı';
        return back()->with('success', "Rule {$ruleId} {$verb}.");
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Audit Event Detail
    // ═══════════════════════════════════════════════════════════════════════

    public function auditEventDetail(int $eventId): JsonResponse
    {
        $event = DB::table('modsecurity_audit_events')->where('id', $eventId)->first();
        if (! $event) {
            return response()->json(['success' => false, 'error' => 'Event not found'], 404);
        }

        $detail = ['request_headers' => null, 'request_body' => null, 'response_headers' => null];
        try {
            $result = $this->sysapi->run('modsec-audit-event-detail', [
                'event_id' => $event->event_id,
            ], timeoutSeconds: 10);
            if ($result->successful) {
                $detail = array_merge($detail, $result->data);
            }
        } catch (\Throwable $e) {
            // soft-fail
        }

        return response()->json([
            'success' => true,
            'event' => $event,
            'matched_data' => $event->matched_data ? json_decode($event->matched_data, true) : null,
            'request_headers' => $detail['request_headers'] ?? null,
            'request_body' => $detail['request_body'] ?? null,
            'response_headers' => $detail['response_headers'] ?? null,
            'all_matched_rules' => $detail['all_matched_rules'] ?? [],
        ]);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Reports
    // ═══════════════════════════════════════════════════════════════════════

    public function reports(Request $request): JsonResponse
    {
        if (! Schema::hasTable('modsecurity_audit_events')) {
            return response()->json([
                'success' => true,
                'daily_trend_30d' => [],
                'hourly_heatmap_7d' => [],
                'category_breakdown' => [],
                'response_code_dist' => [],
                'top_attacked_domains' => [],
                'repeat_offenders' => [],
                'top_countries' => [],
            ]);
        }

        // 30 günlük günlük trend
        $daily_trend_30d = DB::table('modsecurity_audit_events')
            ->selectRaw('DATE(event_at) as day, COUNT(*) as blocked, COUNT(DISTINCT source_ip) as unique_ips')
            ->where('event_at', '>=', now()->subDays(30))
            ->groupBy('day')
            ->orderBy('day')
            ->get();

        // 7 gün × 24 saat heatmap
        $hourly_heatmap_7d = DB::table('modsecurity_audit_events')
            ->selectRaw('DATE(event_at) as day, HOUR(event_at) as hour, COUNT(*) as count')
            ->where('event_at', '>=', now()->subDays(7))
            ->groupBy('day', 'hour')
            ->orderBy('day')
            ->orderBy('hour')
            ->get();

        // Kategori kırılımı (rule_tag prefix bazlı)
        $category_breakdown = DB::table('modsecurity_audit_events')
            ->selectRaw("COALESCE(rule_tag, 'unknown') as category, COUNT(*) as count")
            ->where('event_at', '>=', now()->subDays(7))
            ->groupBy('category')
            ->orderByDesc('count')
            ->limit(10)
            ->get();

        // Response code dağılımı
        $response_code_dist = DB::table('modsecurity_audit_events')
            ->selectRaw('action_taken, COUNT(*) as count')
            ->where('event_at', '>=', now()->subDays(7))
            ->groupBy('action_taken')
            ->get();

        // Top hedef domain
        $top_attacked_domains = DB::table('modsecurity_audit_events')
            ->selectRaw('domain_name, COUNT(*) as attacks, COUNT(DISTINCT source_ip) as unique_attackers')
            ->whereNotNull('domain_name')
            ->where('event_at', '>=', now()->subDays(7))
            ->groupBy('domain_name')
            ->orderByDesc('attacks')
            ->limit(20)
            ->get();

        // Top countries
        $top_countries = DB::table('modsecurity_audit_events')
            ->selectRaw('country_code, COUNT(*) as attacks, COUNT(DISTINCT source_ip) as unique_ips')
            ->whereNotNull('country_code')
            ->where('event_at', '>=', now()->subDays(7))
            ->groupBy('country_code')
            ->orderByDesc('attacks')
            ->limit(15)
            ->get();

        // Repeat offenders (10+ kez tetikleyen IP'ler)
        $repeat_offenders = DB::table('modsecurity_audit_events')
            ->selectRaw('source_ip, country_code, COUNT(*) as hits, COUNT(DISTINCT rule_id) as distinct_rules, MAX(event_at) as last_seen')
            ->where('event_at', '>=', now()->subDays(7))
            ->groupBy('source_ip', 'country_code')
            ->having('hits', '>=', 10)
            ->orderByDesc('hits')
            ->limit(30)
            ->get();

        return response()->json([
            'success' => true,
            'daily_trend_30d' => $daily_trend_30d,
            'hourly_heatmap_7d' => $hourly_heatmap_7d,
            'category_breakdown' => $category_breakdown,
            'response_code_dist' => $response_code_dist,
            'top_attacked_domains' => $top_attacked_domains,
            'top_countries' => $top_countries,
            'repeat_offenders' => $repeat_offenders,
        ]);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // Vendors
    // ═══════════════════════════════════════════════════════════════════════

    public function vendors(): JsonResponse
    {
        if (! Schema::hasTable('modsecurity_vendors')) {
            return response()->json(['success' => true, 'vendors' => []]);
        }

        $vendors = DB::table('modsecurity_vendors')->orderBy('name')->get();

        // Live sync — sysapi'den kurulu durumu doğrula
        try {
            $result = $this->sysapi->run('modsec-vendor-list', [], timeoutSeconds: 5);
            if ($result->successful) {
                $installedSlugs = collect($result->data['vendors'] ?? [])->keyBy('slug');
                $vendors = $vendors->map(function ($v) use ($installedSlugs) {
                    if ($installedSlugs->has($v->slug)) {
                        $live = $installedSlugs->get($v->slug);
                        $v->installed_version = $live['version'] ?? $v->installed_version;
                        $v->rules_count = $live['rules_count'] ?? $v->rules_count;
                        $v->is_installed = true;
                    } else {
                        $v->is_installed = false;
                    }
                    return $v;
                });
            }
        } catch (\Throwable $e) {
            // soft-fail
        }

        return response()->json(['success' => true, 'vendors' => $vendors]);
    }

    public function installVendor(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'slug' => ['required', 'string', 'regex:/^[a-z0-9_]+$/', 'max:50'],
            'source_url' => ['required', 'url'],
        ]);

        try {
            $result = $this->sysapi->run('modsec-vendor-install', $validated, timeoutSeconds: 300);
            if ($result->failed()) {
                return response()->json(['success' => false, 'error' => $result->stderr], 500);
            }

            DB::table('modsecurity_vendors')
                ->where('slug', $validated['slug'])
                ->update([
                    'installed_version' => $result->data['version'] ?? null,
                    'rules_count' => $result->data['rules_count'] ?? 0,
                    'is_enabled' => true,
                    'last_synced_at' => now(),
                    'updated_at' => now(),
                ]);

            return response()->json([
                'success' => true,
                'slug' => $validated['slug'],
                'version' => $result->data['version'] ?? null,
                'rules_count' => $result->data['rules_count'] ?? 0,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 500);
        }
    }

    public function removeVendor(string $slug): RedirectResponse
    {
        if (! preg_match('/^[a-z0-9_]+$/', $slug)) {
            return back()->with('error', 'Geçersiz vendor slug.');
        }

        if ($slug === 'owasp_crs') {
            return back()->with('error', 'OWASP CRS resmi vendor, kaldırılamaz.');
        }

        try {
            $this->sysapi->run('modsec-vendor-remove', ['slug' => $slug], timeoutSeconds: 30);
        } catch (\Throwable $e) {
            Log::warning('modsec.vendor_remove_failed', ['slug' => $slug, 'error' => $e->getMessage()]);
        }

        DB::table('modsecurity_vendors')->where('slug', $slug)->update([
            'is_enabled' => false,
            'installed_version' => null,
            'rules_count' => 0,
            'updated_at' => now(),
        ]);

        return back()->with('success', "Vendor '{$slug}' kaldırıldı.");
    }
}
