#!/usr/bin/env python3
"""
CRIUS COMPRESSOR — DeepSeek Audit Runner via OpenCode
Runs structured prompts sequentially per feature/page.
Usage: python run_audit.py [category] [--dry-run] [--start-from INDEX]
       python run_audit.py security
       python run_audit.py all --start-from 3
"""

import sys
import json
import argparse
from datetime import datetime
from pathlib import Path

# ─── CONFIG ─────────────────────────────────────────────────────────────
OUTPUT_DIR     = Path("audit_results")
PROJECT_ROOT   = "."              # path project Laravel kamu
# ────────────────────────────────────────────────────────────────────────

CONTEXT_HEADER = """
Kamu adalah expert Laravel security & UI auditor.
Project: CRIUS COMPRESSOR — Laravel 12, Tailwind CSS, Alpine.js, Bilingual ID/EN.
Stack: PHP 8.3, Laravel 12, MySQL, Tailwind CSS v3, Alpine.js v3.
Design system: CSS variables --navy, --blue-light, --accent, font Montserrat.
Admin layout: resources/views/layouts/admin.blade.php
Frontend layout: resources/views/layouts/app.blade.php
Helpers: setting(), locale_field(), active_locale() di app/Helpers/helpers.php
Bilingual: field _id / _en suffix, middleware SetLocale, URL prefix /en.

ATURAN OUTPUT KAMU:
1. Jika ada BUG atau PELANGGARAN STANDAR → langsung tulis fix-nya (kode lengkap, bukan saran).
2. Format output: [FILE PATH] → [MASALAH] → [FIX CODE].
3. Jika file sudah benar → tulis "✓ OK: [alasan singkat]".
4. JANGAN tambah penjelasan panjang. Langsung ke kode.
5. Prioritas: CRITICAL > HIGH > MEDIUM > LOW.
""".strip()


# ═══════════════════════════════════════════════════════════════════════════
#  PROMPT BANK — dikelompokkan per kategori
# ═══════════════════════════════════════════════════════════════════════════

PROMPTS = {

  # ┌─────────────────────────────────────────────────────────────────────┐
  # │  01. SECURITY                                                        │
  # └─────────────────────────────────────────────────────────────────────┘
  "security": [

    {
      "id": "SEC-01",
      "title": "CSRF Protection — Semua Form",
      "target_files": [
        "resources/views/frontend/contact/index.blade.php",
        "resources/views/frontend/careers/show.blade.php",
        "resources/views/admin/**/*.blade.php",
      ],
      "prompt": """
TASK: Audit CSRF protection di semua form Blade.

CEK SETIAP FORM:
1. Apakah ada @csrf di setiap <form method="POST">?
2. Apakah ada @method('PUT'/'DELETE') untuk form non-POST?
3. Apakah ada form yang pakai method="GET" tapi submit data sensitif?
4. Cek file: resources/views/frontend/contact/index.blade.php
5. Cek file: resources/views/frontend/careers/show.blade.php
6. Cek SEMUA file di resources/views/admin/ yang punya <form>

UNTUK SETIAP MASALAH: tulis fix lengkap berupa blade snippet yang sudah benar.
Contoh output format:
[resources/views/frontend/contact/index.blade.php:45]
CRITICAL: Form POST tanpa @csrf
FIX:
<form method="POST" action="{{ route('contact.send') }}">
    @csrf
    ...
</form>
""",
    },

    {
      "id": "SEC-02",
      "title": "Authentication & Authorization — Middleware Admin",
      "target_files": [
        "routes/admin.php",
        "app/Http/Middleware/AdminAuth.php",
        "app/Http/Controllers/Admin/DashboardController.php",
      ],
      "prompt": """
TASK: Audit autentikasi & otorisasi admin panel.

CEK:
1. Baca file routes/admin.php — apakah SEMUA route admin sudah dibungkus middleware ['auth', 'admin'] atau setara?
2. Baca app/Http/Middleware/AdminAuth.php — apakah middleware redirect ke login jika belum auth? Apakah cek role user?
3. Apakah ada route admin yang TIDAK terlindungi middleware?
4. Apakah response 403 vs 401 sudah benar (401 = belum login, 403 = tidak punya akses)?
5. Apakah session fixation dicegah setelah login? Cek app/Http/Controllers/Auth/LoginController.php atau equivalent.
6. Apakah ada hardcoded credential di kode?

Tampilkan SEMUA route yang vulnerability beserta fix-nya.
Jika middleware sudah benar, tulis verifikasi bahwa group route sudah terlindungi.
""",
    },

    {
      "id": "SEC-03",
      "title": "File Upload Security",
      "target_files": [
        "app/Services/MediaUploadService.php",
        "app/Http/Controllers/Admin/MediaLibraryController.php",
        "app/Http/Controllers/Admin/ProductController.php",
        "app/Http/Controllers/Admin/BlogPostController.php",
        "app/Http/Controllers/Frontend/CareerController.php",
      ],
      "prompt": """
TASK: Audit keamanan file upload.

CEK SETIAP UPLOAD HANDLER:
1. app/Services/MediaUploadService.php — apakah ada validasi MIME type (bukan hanya ekstensi)?
2. Apakah menggunakan file->getMimeType() atau hanya getClientOriginalExtension()?
3. Apakah file di-rename random (bukan pakai nama asli user)?
4. Apakah ada size limit per upload?
5. Apakah file PDF dari career application (CV upload) di-scan atau minimal di-validasi PDF asli?
6. Apakah storage disk pakai 'public' atau 'local'? File yang bisa diakses public harus HANYA di storage/app/public.
7. Apakah ada risiko path traversal di nama file?
8. Apakah ada validasi bahwa file bukan executable (.php, .exe, .sh)?

WAJIB TULIS FIX untuk:
- Validasi MIME + ekstensi whitelist
- Random filename generator
- Ukuran limit
- Block ekstensi berbahaya

Contoh fix MediaUploadService.php yang aman:
[tulis kode lengkap method store() yang sudah aman]
""",
    },

    {
      "id": "SEC-04",
      "title": "SQL Injection & Mass Assignment",
      "target_files": [
        "app/Models/*.php",
        "app/Http/Controllers/Admin/*.php",
        "app/Http/Requests/Admin/*.php",
      ],
      "prompt": """
TASK: Audit SQL Injection dan Mass Assignment Protection.

CEK:
1. Baca SEMUA Model di app/Models/ — apakah ada $guarded = [] tanpa $fillable yang spesifik?
2. Apakah ada penggunaan DB::statement() atau DB::select() dengan raw string interpolasi user input?
3. Apakah ada ->where(DB::raw($userInput)) atau sejenisnya?
4. Cek app/Http/Requests/Admin/StoreProductRequest.php — apakah rules() sudah lengkap untuk semua field?
5. Apakah controller pakai $request->all() langsung ke Model::create() tanpa Form Request?
6. Apakah ada N+1 query problem di controller (loop yang trigger query di dalam loop)?

UNTUK SETIAP MODEL: tampilkan $fillable yang benar.
UNTUK SETIAP CONTROLLER: tampilkan jika ada raw query yang tidak aman.

Contoh fix mass assignment:
// SALAH
Product::create($request->all());

// BENAR (pakai Form Request yang sudah validated)
Product::create($request->validated());
""",
    },

    {
      "id": "SEC-05",
      "title": "XSS Prevention — Blade Output",
      "target_files": [
        "resources/views/frontend/**/*.blade.php",
        "resources/views/admin/**/*.blade.php",
      ],
      "prompt": """
TASK: Audit XSS (Cross-Site Scripting) di semua Blade views.

CEK:
1. Apakah ada penggunaan {!! $variable !!} (unescaped output) yang tidak aman?
2. {!! !!} HANYA boleh dipakai untuk:
   - Konten rich-text dari WYSIWYG editor (blog content, service content) yang sudah di-sanitize
   - HTML yang dihasilkan sendiri oleh sistem (bukan dari user input langsung)
3. Apakah ada $variable yang berasal dari user input di-render dengan {!! !!}?
4. Cek khusus: resources/views/frontend/blog/show.blade.php (blog content)
5. Cek khusus: resources/views/frontend/products/show.blade.php (product description)
6. Apakah ada inline JavaScript yang embed PHP variable tanpa json_encode()?

Contoh masalah:
{!! $product->description_id !!}  ← apakah ini dari WYSIWYG? Perlu sanitize.
<script>var data = "{{ $userInput }}";</script>  ← XSS via JS context.

FIX untuk rich-text content:
// Di controller sebelum pass ke view:
$post->content_id = clean($post->content_id); // pakai HTMLPurifier atau strip_tags

// Di blade untuk trusted HTML:
{!! $post->content_id !!}

// Di blade untuk user-facing data:
{{ $message->message }}  ← gunakan double curly untuk auto-escape
""",
    },

    {
      "id": "SEC-06",
      "title": "Rate Limiting & Contact Form Spam",
      "target_files": [
        "routes/web.php",
        "app/Http/Controllers/Frontend/ContactController.php",
        "app/Http/Controllers/Frontend/CareerController.php",
        "app/Http/Requests/Frontend/ContactFormRequest.php",
        "app/Http/Requests/Frontend/CareerApplyRequest.php",
      ],
      "prompt": """
TASK: Audit rate limiting dan anti-spam untuk form publik.

CEK:
1. routes/web.php — apakah route POST /contact dan POST /careers/{id}/apply punya throttle middleware?
   Contoh yang benar: Route::post('/contact', ...)->middleware('throttle:5,1'); // 5 request per menit
2. app/Http/Requests/Frontend/ContactFormRequest.php — apakah ada validasi:
   - email: required|email|max:255
   - message: required|min:10|max:5000
   - phone: nullable|regex:/^[0-9+\\-\\s\\(\\)]{7,20}$/
   - name: required|string|max:255
   - honeypot field untuk bot detection?
3. Apakah IP address disimpan di contact_messages table?
4. Apakah ada duplicate submission check (same email + same message dalam 1 menit)?
5. Apakah file upload CV di career hanya accept PDF dan max 2MB?

TULIS FIX LENGKAP untuk:
- throttle di routes/web.php
- rules() lengkap di ContactFormRequest.php
- rules() lengkap di CareerApplyRequest.php dengan file validation
""",
    },

    {
      "id": "SEC-07",
      "title": "Environment & Config Security",
      "target_files": [
        ".env.example",
        "config/crius.php",
        "config/app.php",
        "bootstrap/app.php",
      ],
      "prompt": """
TASK: Audit konfigurasi keamanan environment & config.

CEK:
1. .env.example — apakah semua key sensitif ada di sini (tanpa nilai asli)?
   Wajib ada: APP_KEY, DB_PASSWORD, MAIL_PASSWORD, GA4_MEASUREMENT_ID, WHATSAPP_NUMBER
2. config/app.php atau bootstrap/app.php — apakah APP_DEBUG=false di production setup?
3. Apakah ada hardcoded API key, password, atau secret di file PHP manapun (bukan .env)?
4. config/crius.php — apakah nilai sensitif diambil dari env() bukan hardcoded?
5. Apakah APP_URL di .env.example sudah https://?
6. Apakah ada session config yang aman? (session.secure = true di production)
7. Apakah cookie httponly dan secure?

TULIS:
- .env.example yang lengkap dan aman
- Snippet config/session.php yang hardened untuk production
- Checklist deployment security (format komentar PHP)
""",
    },

    {
      "id": "SEC-08",
      "title": "Admin Media Library — Path Traversal & Deletion",
      "target_files": [
        "app/Http/Controllers/Admin/MediaLibraryController.php",
      ],
      "prompt": """
TASK: Audit MediaLibraryController untuk path traversal dan unauthorized file deletion.

CEK:
1. Method upload() — apakah filename di-sanitize? Tidak boleh ada '../' atau absolute path di nama file.
2. Method delete() — apakah ada validasi bahwa file yang dihapus HANYA boleh dari directory storage/app/public/ proyek ini?
3. Apakah ada cek bahwa user yang hapus adalah admin yang terautentikasi?
4. Apakah ada symlink traversal vulnerability?
5. Apakah response error tidak leak server path?

Contoh fix path traversal:
// SALAH
$path = 'uploads/' . $request->filename;
Storage::delete($path);

// BENAR
$filename = basename($request->filename); // strip path components
$path = 'products/' . $filename;
if (!Storage::disk('public')->exists($path)) {
    abort(404);
}
Storage::disk('public')->delete($path);

Tulis method upload() dan delete() yang lengkap dan aman.
""",
    },

  ],

  # ┌─────────────────────────────────────────────────────────────────────┐
  # │  02. ADMIN UI STANDARDS                                              │
  # └─────────────────────────────────────────────────────────────────────┘
  "admin_ui": [

    {
      "id": "ADM-01",
      "title": "Admin Table — Responsif & Standar CSS Class",
      "target_files": [
        "resources/views/admin/products/index.blade.php",
        "resources/views/admin/blog/posts/index.blade.php",
        "resources/views/admin/careers/index.blade.php",
        "resources/views/admin/contact-messages/index.blade.php",
        "resources/views/admin/team/index.blade.php",
        "resources/views/admin/testimonials/index.blade.php",
      ],
      "prompt": """
TASK: Audit dan fix responsivitas tabel di semua halaman admin index.

STANDAR WAJIB TABEL ADMIN (dari admin.css):
1. Setiap tabel HARUS dibungkus: <div class="admin-table-wrap"><table>...</table></div>
2. Wrapper luar: <div class="admin-table-card">
3. Header tabel: <div class="admin-table-head"><h3 class="admin-table-title">...</h3> + tombol Add</div>
4. Kolom action WAJIB: <td class="admin-table-actions">
5. Tombol action format: <a class="action-btn action-btn--edit">Edit</a> <button class="action-btn action-btn--danger">Hapus</button>
6. Badge status: <span class="badge badge--blue">Aktif</span> atau <span class="badge badge--inactive">Nonaktif</span>
7. JANGAN pakai inline style width di <td> untuk kolom action — gunakan class admin-table-actions

CEK SETIAP FILE:
- Apakah ada admin-table-wrap? Jika tidak → tambahkan.
- Apakah kolom action pakai class admin-table-actions? Jika tidak → fix.
- Apakah ada <table> tanpa wrapper yang menyebabkan overflow di mobile?
- Apakah thead th sudah pakai font-mono tracking style yang sesuai admin.css?

TULIS fix lengkap untuk setiap tabel yang tidak memenuhi standar.
Format tabel yang BENAR:
<div class="admin-table-card">
  <div class="admin-table-head">
    <h3 class="admin-table-title">Daftar [Resource]</h3>
    <a href="{{ route('admin.[resource].create') }}" class="btn btn--primary btn--sm">+ Tambah</a>
  </div>
  <div class="admin-table-wrap">
    <table>
      <thead>
        <tr>
          <th>NAMA</th>
          <th>STATUS</th>
          <th class="admin-table-actions">AKSI</th>
        </tr>
      </thead>
      <tbody>
        @foreach($items as $item)
        <tr>
          <td>{{ $item->name_id }}</td>
          <td><span class="badge {{ $item->is_active ? 'badge--blue' : 'badge--inactive' }}">{{ $item->is_active ? 'Aktif' : 'Nonaktif' }}</span></td>
          <td class="admin-table-actions">
            <a href="{{ route('admin.[resource].edit', $item) }}" class="action-btn action-btn--edit">Edit</a>
            <form method="POST" action="{{ route('admin.[resource].destroy', $item) }}" style="display:inline" onsubmit="return confirm('Hapus?')">
              @csrf @method('DELETE')
              <button type="submit" class="action-btn action-btn--danger">Hapus</button>
            </form>
          </td>
        </tr>
        @endforeach
      </tbody>
    </table>
  </div>
</div>
""",
    },

    {
      "id": "ADM-02",
      "title": "Admin Form — Standar Input & Validasi Error Display",
      "target_files": [
        "resources/views/admin/products/create.blade.php",
        "resources/views/admin/products/edit.blade.php",
        "resources/views/admin/blog/posts/form.blade.php",
        "resources/views/admin/services/form.blade.php",
        "resources/views/admin/careers/form.blade.php",
      ],
      "prompt": """
TASK: Audit standarisasi form di halaman admin.

STANDAR WAJIB FORM ADMIN:
1. Wrapper: <div class="admin-form-card">
2. Header section: <div class="admin-form-header"><h3 class="admin-form-section">Informasi [Nama]</h3></div>
3. Body: <div class="admin-form-body">
4. Footer button: <div class="admin-form-footer">
5. SETIAP input WAJIB tampilkan error Laravel: @error('field_name')<p class="text-red-500 text-xs mt-1">{{ $message }}</p>@enderror
6. Input bilingual (ID/EN) WAJIB ditampilkan berdampingan dalam grid 2 kolom atau dengan tab ID/EN
7. Toggle switch untuk is_active, is_featured menggunakan Alpine.js x-model, bukan checkbox biasa
8. Semua file input (image) HARUS tampilkan preview setelah file dipilih (Alpine.js atau JS)
9. Breadcrumb di atas form: Home → [Resource] → Tambah/Edit

CEK SETIAP FORM FILE:
- Apakah semua @error() sudah ada di bawah setiap field?
- Apakah value="{{ old('field', $model->field ?? '') }}" sudah ada di setiap input?
- Apakah form punya @csrf?
- Apakah form edit punya @method('PUT')?
- Apakah field bilingual tertata rapi (tidak 1 kolom penuh)?

Tulis fix untuk form yang tidak memenuhi standar.
Contoh input standar:
<div class="admin-form-body">
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
    <div>
      <label class="block text-sm font-semibold text-crius-navy mb-2">Nama (ID) <span class="text-red-500">*</span></label>
      <input type="text" name="name_id" value="{{ old('name_id', $product->name_id ?? '') }}"
             class="w-full border border-crius-grey-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-crius-blue-light"
             placeholder="Nama dalam Bahasa Indonesia">
      @error('name_id')<p class="text-red-500 text-xs mt-1">{{ $message }}</p>@enderror
    </div>
    <div>
      <label class="block text-sm font-semibold text-crius-navy mb-2">Name (EN) <span class="text-red-500">*</span></label>
      <input type="text" name="name_en" value="{{ old('name_en', $product->name_en ?? '') }}"
             class="w-full border border-crius-grey-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-crius-blue-light"
             placeholder="Name in English">
      @error('name_en')<p class="text-red-500 text-xs mt-1">{{ $message }}</p>@enderror
    </div>
  </div>
</div>
""",
    },

    {
      "id": "ADM-03",
      "title": "Admin Dashboard — Stats & Quick Actions",
      "target_files": [
        "resources/views/admin/dashboard/index.blade.php",
        "app/Http/Controllers/Admin/DashboardController.php",
      ],
      "prompt": """
TASK: Audit dashboard admin — stat cards, data aktual, dan performa query.

CEK DashboardController.php:
1. Apakah semua stat diambil dari DB (bukan hardcoded)?
   Wajib ada: Product::count(), BlogPost::where('status','published')->count(),
   ContactMessage::where('is_read',false)->count(), Career::where('is_active',true)->count(),
   CareerApplication::whereDate('created_at', today())->count()
2. Apakah query dioptimasi? Tidak boleh ada multiple query yang bisa digabung.
3. Apakah ada eager loading yang hilang?

CEK dashboard/index.blade.php:
1. Apakah stat card pakai class admin-stat-card, admin-stat-icon, admin-stat-num, admin-stat-label?
2. Apakah ada link "Pesan baru (X)" yang menuju ke /admin/contact-messages?
3. Apakah ada tabel "Pesan Terbaru" (5 pesan terakhir) dengan link ke detail?
4. Apakah ada tabel "Produk Terbaru" (5 produk terakhir)?
5. Apakah ada "Lamaran Karir Baru" counter?

Tulis fix DashboardController yang optimal:
public function index() {
    $stats = [
        'products'      => Product::count(),
        'published_posts' => BlogPost::published()->count(),
        'unread_messages' => ContactMessage::unread()->count(),
        'active_careers'  => Career::active()->count(),
        'new_applications'=> CareerApplication::today()->count(),
    ];
    $recent_messages = ContactMessage::latest()->limit(5)->get();
    $recent_products = Product::with('category')->latest()->limit(5)->get();
    return view('admin.dashboard.index', compact('stats', 'recent_messages', 'recent_products'));
}
""",
    },

    {
      "id": "ADM-04",
      "title": "Admin Settings — CMS Groups",
      "target_files": [
        "resources/views/admin/settings/homepage.blade.php",
        "resources/views/admin/settings/general.blade.php",
        "resources/views/admin/settings/contact.blade.php",
        "resources/views/admin/settings/seo.blade.php",
        "resources/views/admin/settings/integrations.blade.php",
        "app/Http/Controllers/Admin/SiteSettingController.php",
      ],
      "prompt": """
TASK: Audit halaman settings admin — completeness dan UX.

CEK SiteSettingController.php:
1. Method edit($group) — apakah query settings berdasarkan group sudah benar?
   Setting::where('group', $group)->orderBy('sort_order')->get()
2. Method update($group) — apakah update pakai updateOrCreate() atau loop per key?
3. Apakah image settings disimpan sebagai path (bukan base64)?
4. Apakah ada flash message setelah save berhasil?

CEK settings/general.blade.php:
1. Apakah ada field: site_name, logo (dengan preview), favicon, whatsapp_number?
2. Apakah image upload field tampilkan preview gambar saat ini?
3. Apakah ada tombol "Hapus gambar" untuk logo/favicon?

CEK settings/homepage.blade.php:
1. Apakah ada editor untuk hero_slides (JSON)? Minimal textarea dengan validasi JSON.
2. Apakah bisa tambah/hapus slide secara visual (bukan edit raw JSON)?

CEK settings/integrations.blade.php:
1. Apakah ada field ga4_measurement_id dengan helper text "Format: G-XXXXXXXXXX"?
2. Apakah ada field whatsapp_number dengan format hint "+628xxx"?
3. Apakah ada field google_maps_api_key dengan input type="password" agar tidak terekspos?

Tulis fix untuk bagian yang kurang.
""",
    },

    {
      "id": "ADM-05",
      "title": "Admin Sidebar — Active State & Mobile Toggle",
      "target_files": [
        "resources/views/layouts/partials/admin/sidebar.blade.php",
        "resources/views/layouts/admin.blade.php",
      ],
      "prompt": """
TASK: Audit sidebar admin — active state, mobile responsivitas, dan struktur navigasi.

STANDAR dari admin.css:
- .admin-nav-item.active → wajib highlight
- .admin-sidebar → fixed, w-64, bisa collapsed ke 72px
- .admin-main → ml-64, transition

CEK sidebar.blade.php:
1. Apakah class "active" di-set dengan benar menggunakan request()->routeIs() atau request()->is()?
   Contoh benar: class="{{ request()->routeIs('admin.products*') ? 'admin-nav-item active' : 'admin-nav-item' }}"
2. Apakah semua menu grup sudah ada? Grup wajib: Produk, Konten, Marketing, Pengaturan, Sistem
3. Apakah ada nav-label untuk setiap grup?
4. Apakah icon nav menggunakan SVG inline (bukan font icon)?

CEK admin.blade.php:
1. Apakah ada toggle sidebar untuk mobile (hamburger)?
2. Apakah ada overlay ketika sidebar terbuka di mobile?
3. Apakah Alpine.js dipakai untuk toggle state sidebar?
   x-data="{ sidebarOpen: false }"

FIX sidebar active state — tulis snippet lengkap untuk group Produk:
<div class="admin-nav-group">
  <p class="admin-nav-label">PRODUK</p>
  <a href="{{ route('admin.products.index') }}"
     class="{{ request()->routeIs('admin.products*') ? 'admin-nav-item active' : 'admin-nav-item' }}">
    <svg class="admin-nav-icon" ...>[ikon produk]</svg>
    <span>Produk</span>
  </a>
  ...
</div>

Tulis fix untuk semua masalah yang ditemukan.
""",
    },

  ],

  # ┌─────────────────────────────────────────────────────────────────────┐
  # │  03. FRONTEND UX STANDARDS                                           │
  # └─────────────────────────────────────────────────────────────────────┘
  "frontend_ux": [

    {
      "id": "FE-01",
      "title": "Homepage — Sections Completeness & CMS Integration",
      "target_files": [
        "resources/views/frontend/home/index.blade.php",
        "app/Http/Controllers/Frontend/HomeController.php",
      ],
      "prompt": """
TASK: Audit homepage — kelengkapan section dan integrasi CMS.

CEK HomeController.php:
1. Apakah semua data diambil dari DB? Wajib:
   - $heroSlides = json_decode(setting('hero_slides'), true)
   - $featuredProducts = Product::active()->featured()->with(['category','brand'])->ordered()->limit(6)->get()
   - $categories = ProductCategory::active()->ordered()->whereNull('parent_id')->get()
   - $stats = StatsCounter::active()->ordered()->get()
   - $testimonials = Testimonial::active()->ordered()->get()
   - $brands = Brand::active()->ordered()->get()
   - $latestPosts = BlogPost::published()->with('category')->latest('published_at')->limit(3)->get()
   - $industries = json_decode(setting('homepage_industries'), true) ?? []
   Jika ada yang missing → tambahkan.

CEK home/index.blade.php:
1. Apakah semua 14 section dari blueprint ada? (Topbar, Navbar, Hero, Stats, Kategori, Produk, Why, Industri, Brands, Testimoni, Blog, CTA, Footer, WA Float)
2. Apakah hero slider pakai Alpine.js dengan auto-play?
   x-data="heroSlider()" x-init="init()"
3. Apakah stats counter ada animasi counting saat masuk viewport?
4. Apakah floating WhatsApp muncul setelah scroll 100px?
5. Apakah brand carousel (marquee) berjalan otomatis?
6. Apakah testimonial carousel ada auto-play?

Tulis fix untuk section yang missing atau tidak terintegrasi dengan DB.
Tulis Alpine.js component heroSlider() yang benar.
""",
    },

    {
      "id": "FE-02",
      "title": "Product Detail — Tabs, Specs Table, Gallery",
      "target_files": [
        "resources/views/frontend/products/show.blade.php",
        "app/Http/Controllers/Frontend/ProductController.php",
      ],
      "prompt": """
TASK: Audit halaman detail produk — gallery, tabs, specs, CTA.

CEK ProductController.php method show():
1. Apakah $product di-load dengan eager loading semua relasi?
   Product::with(['category','brand','images','specs','highlights'])->where('slug',$slug)->firstOrFail()
2. Apakah specs digroup? $groupedSpecs = $product->specs->groupBy('spec_group')
3. Apakah related products diambil? Product::where('category_id', $product->category_id)->where('id','!=',$product->id)->active()->limit(4)->get()
4. Apakah WhatsApp link dibuat dinamis? 
   $waLink = 'https://wa.me/' . setting('whatsapp_number') . '?text=' . urlencode('Halo, saya tertarik dengan produk: ' . $product->{'name_' . app()->getLocale()})

CEK products/show.blade.php:
1. Apakah gallery thumbnail bisa diklik untuk ganti gambar utama? (Alpine.js)
2. Apakah tabs Overview/Specifications/Applications berfungsi dengan Alpine.js?
3. Apakah specs table digroup per spec_group (Performance, Technical, Physical, Electrical)?
4. Apakah key highlights (product_highlights) tampil sebagai kotak metric di atas tabs?
5. Apakah ada tombol "Request Quote via WhatsApp" yang link-nya dinamis dari DB?
6. Apakah ada breadcrumb: Home → Produk → [Kategori] → [Nama Produk]?

Tulis Alpine.js tabs component:
x-data="{ activeTab: 'overview' }"
<button @click="activeTab = 'overview'" :class="activeTab === 'overview' ? 'tab-active' : ''">Overview</button>
<div x-show="activeTab === 'overview'">...</div>

Tulis Alpine.js gallery component.
""",
    },

    {
      "id": "FE-03",
      "title": "Contact Form — UX, Validation & Email",
      "target_files": [
        "resources/views/frontend/contact/index.blade.php",
        "app/Http/Controllers/Frontend/ContactController.php",
        "app/Http/Requests/Frontend/ContactFormRequest.php",
        "app/Mail/ContactInquiryMail.php",
        "app/Mail/ContactAutoReplyMail.php",
      ],
      "prompt": """
TASK: Audit halaman kontak — form UX, validasi, dan pengiriman email.

CEK ContactController.php:
1. Method send() — apakah menyimpan ke contact_messages table?
2. Apakah kirim email ke admin DAN auto-reply ke pengirim?
3. Apakah ada try-catch untuk email send yang gagal? (email gagal tidak boleh crash form)
4. Apakah ip_address tersimpan? $request->ip()
5. Apakah setelah submit redirect back dengan success message?

CEK ContactFormRequest.php:
1. Apakah rules() lengkap untuk: name, email, phone, company, subject, message, product_id?
2. Apakah messages() dalam bahasa yang sesuai locale?
3. Apakah ada honeypot field? (field tersembunyi, jika diisi = bot)

CEK contact/index.blade.php:
1. Apakah ada @if(session('success'))<div class="alert-success">{{ session('success') }}</div>@endif?
2. Apakah form field tampilkan error Laravel per-field?
3. Apakah dropdown produk diisi dari DB? <select name="product_id"> dengan list produk aktif.
4. Apakah ada loading state di tombol submit saat form dikirim? (Alpine.js)
5. Apakah Google Maps iframe diambil dari setting('contact_maps_embed_url')?

Tulis fix lengkap untuk ContactController@send() yang benar:
public function send(ContactFormRequest $request) {
    // simpan ke DB
    // kirim email dengan exception handling
    // redirect dengan flash message
}
""",
    },

    {
      "id": "FE-04",
      "title": "Blog — SEO, Pagination & Sidebar",
      "target_files": [
        "resources/views/frontend/blog/index.blade.php",
        "resources/views/frontend/blog/show.blade.php",
        "app/Http/Controllers/Frontend/BlogController.php",
      ],
      "prompt": """
TASK: Audit halaman blog — SEO, pagination, dan sidebar.

CEK BlogController.php:
1. method index() — apakah ada filter per kategori via query param?
   BlogPost::published()->when($request->category, fn($q,$c) => $q->whereHas('category', fn($q) => $q->where('slug',$c)))->paginate(9)
2. method show() — apakah ada increment view_count? (opsional tapi direkomendasikan)
3. Apakah related posts diambil dari kategori yang sama?
4. Apakah $seoData disiapkan untuk tiap post?

CEK blog/show.blade.php:
1. Apakah ada meta tag untuk SEO? Cek layout seo-head.blade.php dipanggil dengan benar.
2. Apakah ada Open Graph tags (og:title, og:image, og:description)?
3. Apakah tanggal format sesuai locale? (format ID: "01 Januari 2025", EN: "January 01, 2025")
4. Apakah ada share buttons (WhatsApp, Facebook, Twitter)?
5. Apakah konten blog render dengan {!! $post->{'content_' . app()->getLocale()} !!}?
6. Apakah ada "Baca juga" (related posts) section?

CEK blog/index.blade.php:
1. Apakah pagination link muncul? {{ $posts->links() }}
2. Apakah ada featured post (is_featured=true) ditampilkan lebih besar?
3. Apakah filter kategori berfungsi (active class pada tab kategori aktif)?

Tulis fix untuk semua masalah yang ditemukan.
""",
    },

    {
      "id": "FE-05",
      "title": "Bilingual (ID/EN) — SetLocale Middleware & Switcher",
      "target_files": [
        "app/Http/Middleware/SetLocale.php",
        "resources/views/layouts/partials/lang-switcher.blade.php",
        "app/Http/Controllers/Frontend/LanguageSwitchController.php",
        "app/Helpers/helpers.php",
        "routes/web.php",
      ],
      "prompt": """
TASK: Audit implementasi bilingual ID/EN — konsistensi di seluruh aplikasi.

CEK SetLocale.php:
1. Apakah middleware baca locale dari: (1) session, (2) URL segment /en, secara prioritas?
2. Apakah App::setLocale() dipanggil dengan benar?
3. Apakah locale disimpan ke session?

CEK helpers.php:
1. Apakah function locale_field($field) ada dan benar?
   function locale_field($field) { return $field . '_' . (app()->getLocale() === 'en' ? 'en' : 'id'); }
2. Apakah function setting($key, $default = null) ada?
3. Apakah function active_locale() ada?
4. Apakah function is_locale_en() ada?

CEK lang-switcher.blade.php:
1. Apakah link switcher generate URL dengan locale yang benar?
   Contoh: dari /products/slug → ke /en/products/slug (atau via /lang/en)
2. Apakah current locale ditandai aktif secara visual?
3. Apakah ada data-saver (hanya muat flag icon sebagai teks/emoji bukan gambar)?

CEK LanguageSwitchController:
1. Apakah switch locale menyimpan ke session DAN redirect ke halaman sebelumnya?

CEK routes/web.php:
1. Apakah ada prefix '/en' untuk versi Inggris? ATAU apakah pakai session-only approach?
2. Konsistensi: pastikan semua route bilingual menggunakan pola yang sama.

CEK random di 3 view frontend:
1. Apakah semua teks dinamis pakai locale_field()? Bukan hardcode ->name_id.
2. Apakah static string (label tombol, dll) pakai __('common.key') atau lang file?

Tulis fix untuk semua inkonsistensi yang ditemukan.
""",
    },

    {
      "id": "FE-06",
      "title": "SEO — Meta Tags, OG, Sitemap, Structured Data",
      "target_files": [
        "resources/views/layouts/partials/seo-head.blade.php",
        "app/Services/SeoService.php",
        "app/Console/Commands/GenerateSitemap.php",
        "resources/views/layouts/app.blade.php",
      ],
      "prompt": """
TASK: Audit SEO implementation — meta tags, Open Graph, sitemap, dan structured data.

CEK seo-head.blade.php:
1. Apakah semua tag ini ada?
   <title>{{ $seo['title'] ?? setting('default_meta_title_' . app()->getLocale()) }}</title>
   <meta name="description" content="...">
   <meta property="og:title" content="...">
   <meta property="og:description" content="...">
   <meta property="og:image" content="...">
   <meta property="og:url" content="{{ url()->current() }}">
   <meta property="og:type" content="website"> (atau "article" untuk blog)
   <link rel="canonical" href="{{ url()->current() }}">
   <meta name="robots" content="index, follow"> (atau noindex untuk admin)
2. Apakah lang attribute di <html> diset ke locale aktif?
   <html lang="{{ app()->getLocale() }}">
3. Apakah ada hreflang untuk bilingual?
   <link rel="alternate" hreflang="id" href="{{ url()->current() }}">
   <link rel="alternate" hreflang="en" href="{{ url('en' . request()->getPathInfo()) }}">

CEK SeoService.php:
1. Apakah ada method generateProductSchema() untuk JSON-LD Product schema?
2. Apakah ada method generateArticleSchema() untuk blog posts?
3. Apakah ada method generateOrganizationSchema() untuk homepage?

CEK GenerateSitemap.php:
1. Apakah generate sitemap.xml yang include semua URL: produk, blog, layanan, halaman statis?
2. Apakah ada loc, lastmod, changefreq, priority per URL?
3. Apakah sitemap disimpan ke public/sitemap.xml?

Tulis fix untuk semua masalah SEO yang ditemukan.
Tulis JSON-LD Product schema untuk halaman produk:
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "{{ $product->{'name_' . app()->getLocale()} }}",
  ...
}
</script>
""",
    },

  ],

  # ┌─────────────────────────────────────────────────────────────────────┐
  # │  04. CODE STANDARDS                                                  │
  # └─────────────────────────────────────────────────────────────────────┘
  "code_standards": [

    {
      "id": "STD-01",
      "title": "Model Scopes & Relationships — Kelengkapan",
      "target_files": [
        "app/Models/Product.php",
        "app/Models/BlogPost.php",
        "app/Models/Career.php",
        "app/Models/ContactMessage.php",
        "app/Models/SiteSetting.php",
      ],
      "prompt": """
TASK: Audit model scopes, relationships, dan accessors.

STANDAR WAJIB SETIAP MODEL:
1. Scope active(): scopeActive($query) { return $query->where('is_active', true); }
2. Scope ordered(): scopeOrdered($query) { return $query->orderBy('sort_order'); }
3. Semua relasi harus ada dengan return type hint
4. Accessor untuk computed property

CEK Product.php:
1. Apakah ada: scopeActive, scopeOrdered, scopeFeatured?
2. Apakah ada relasi: category(), brand(), images(), specs(), highlights()?
3. Apakah ada accessor getWhatsappLinkAttribute()?
4. Apakah ada accessor getThumbnailUrlAttribute() yang return full URL?
5. Apakah $fillable sudah lengkap sesuai ERD?

CEK BlogPost.php:
1. Apakah ada: scopePublished() { where('status','published')->where('published_at','<=',now()) }?
2. Apakah ada: scopeFeatured()?
3. Apakah ada relasi: category(), author(), tags()?
4. Apakah ada accessor getExcerptAttribute() yang fallback ke substr content?

CEK Career.php:
1. Apakah ada scopeActive() yang juga cek expired_at > now()?
   where('is_active',true)->where(fn($q) => $q->whereNull('expired_at')->orWhere('expired_at','>',now()))
2. Apakah ada relasi applications()?

CEK ContactMessage.php:
1. Apakah ada scopeUnread()?
2. Apakah ada method markAsRead()?

Tulis fix lengkap untuk setiap model yang kurang.
""",
    },

    {
      "id": "STD-02",
      "title": "Controller — Response Consistency & Error Handling",
      "target_files": [
        "app/Http/Controllers/Admin/ProductController.php",
        "app/Http/Controllers/Admin/BlogPostController.php",
        "app/Http/Controllers/Admin/SiteSettingController.php",
      ],
      "prompt": """
TASK: Audit konsistensi response dan error handling di controller admin.

STANDAR WAJIB ADMIN CONTROLLER:
1. store() berhasil → redirect()->route('admin.X.index')->with('success', 'Berhasil disimpan.')
2. update() berhasil → redirect()->route('admin.X.edit', $model)->with('success', 'Berhasil diperbarui.')
3. destroy() berhasil → redirect()->route('admin.X.index')->with('success', 'Berhasil dihapus.')
4. SETIAP operasi yang bisa gagal HARUS ada try-catch
5. Image/file delete saat model di-destroy: Storage::delete($model->thumbnail)
6. Slug auto-generate dari name_id jika slug kosong
7. Pagination di index(): ->paginate(15)

CEK ProductController.php:
1. Apakah store() auto-generate slug dari name_id?
2. Apakah destroy() hapus file gambar dari storage sebelum delete model?
3. Apakah ada authorize() check atau policy?
4. Apakah ada try-catch di store/update?

CEK BlogPostController.php:
1. Apakah published_at di-set ke now() saat status diubah ke 'published'?
2. Apakah thumbnail lama dihapus saat upload thumbnail baru?

CEK SiteSettingController.php:
1. Apakah update() loop semua key dan updateOrCreate()?
2. Apakah image type disimpan sebagai path relative (bukan full URL)?

Tulis fix untuk semua inkonsistensi.
""",
    },

    {
      "id": "STD-03",
      "title": "Blade Layout — Consistency & Component Reuse",
      "target_files": [
        "resources/views/layouts/app.blade.php",
        "resources/views/layouts/admin.blade.php",
        "resources/views/layouts/partials/navbar.blade.php",
        "resources/views/layouts/partials/footer.blade.php",
        "resources/views/layouts/partials/whatsapp-float.blade.php",
      ],
      "prompt": """
TASK: Audit Blade layout — konsistensi struktur dan reusability.

CEK app.blade.php:
1. Apakah include partial dalam urutan benar: seo-head → navbar → @yield('content') → footer → whatsapp-float?
2. Apakah GA4 script di-inject di sini? if(setting('ga4_measurement_id')) {{ -- inject GA4 script -- }}
3. Apakah ada @stack('scripts') sebelum </body>?
4. Apakah ada @stack('styles') di dalam <head>?
5. Apakah Vite assets di-load dengan @vite(['resources/css/app.css', 'resources/js/app.js'])?

CEK navbar.blade.php:
1. Apakah menu navigasi diambil dari DB? Menu::header()->active()->ordered()->get()
   ATAU dari static route — mana yang dipakai? Jika static, apakah sudah sinkron dengan routes?
2. Apakah active state nav link berfungsi? request()->routeIs('products*') dsb.
3. Apakah mobile hamburger menu berfungsi dengan Alpine.js?
4. Apakah language switcher ada dan berfungsi?
5. Apakah CTA button "Request Quote" link ke WhatsApp dari setting()?

CEK footer.blade.php:
1. Apakah copyright tahun otomatis? {{ date('Y') }}
2. Apakah link sosial media dari setting('social_*')?
3. Apakah ada link sitemap dan kebijakan privasi?

CEK whatsapp-float.blade.php:
1. Apakah tombol hanya muncul setelah scroll 100px? (Alpine.js atau JS)
2. Apakah nomor WA dari setting('whatsapp_number')?
3. Apakah pesan default dari setting('whatsapp_message_' . app()->getLocale())?
4. Apakah ada animasi pulse?

Tulis fix untuk setiap masalah.
""",
    },

  ],

  # ┌─────────────────────────────────────────────────────────────────────┐
  # │  05. PERFORMANCE                                                     │
  # └─────────────────────────────────────────────────────────────────────┘
  "performance": [

    {
      "id": "PERF-01",
      "title": "N+1 Query & Eager Loading Audit",
      "target_files": [
        "app/Http/Controllers/Frontend/HomeController.php",
        "app/Http/Controllers/Frontend/ProductController.php",
        "app/Http/Controllers/Frontend/BlogController.php",
      ],
      "prompt": """
TASK: Audit N+1 query problem dan eager loading di semua controller frontend.

CARA CEK: Cari semua ->get() atau ->paginate() yang diikuti akses relasi di view.

CEK HomeController.php index():
1. Apakah featuredProducts di-load dengan: Product::with(['category','brand','images' => fn($q) => $q->where('is_primary',true)])->featured()->active()->get()?
2. Apakah ada akses $product->category->name di view TANPA with('category')? → N+1!

CEK ProductController.php index():
1. Apakah ada with(['category','brand']) saat list produk?
2. Apakah ada with(['images']) yang load SEMUA gambar padahal hanya butuh thumbnail?
   FIX: with(['primaryImage']) atau eager load hanya is_primary=true

CEK ProductController.php show():
1. Apakah semua relasi di-load sekaligus?
   Product::with(['category','brand','images','specs','highlights'])->...

CEK BlogController.php:
1. Apakah posts di-load dengan with(['category','author'])?
2. Apakah ada load tags di listing? (tidak perlu, hanya di detail)

TULIS:
1. Semua query yang punya N+1 problem beserta fixnya
2. Versi query optimal untuk setiap controller method
3. Rekomendasi apakah perlu cache (SiteSettings sebaiknya di-cache)

Contoh fix cache settings:
// Di SiteSettingService.php
public function get($key) {
    return Cache::rememberForever('setting_' . $key, fn() =>
        SiteSetting::where('key', $key)->value('value')
    );
}
// Invalidate di SiteSettingController update():
Cache::forget('setting_' . $key);
""",
    },

  ],

}


# ═══════════════════════════════════════════════════════════════════════════
#  RUNNER
# ═══════════════════════════════════════════════════════════════════════════

def run_prompt(prompt_data: dict, category: str, dry_run: bool = False) -> dict:
    full_prompt = f"{CONTEXT_HEADER}\n\n{'='*60}\n{prompt_data['prompt'].strip()}"
    prompt_id = prompt_data["id"]
    prompt_title = prompt_data["title"]
    target_files = prompt_data["target_files"]

    if dry_run:
        print(f"  [DRY RUN] Would run: {prompt_id} — {prompt_title}")
        print(f"  Target files: {', '.join(target_files[:2])}...")
        return {"id": prompt_id, "status": "dry_run"}

    # ── Save prompt to file (agent will process it) ──
    cat_dir = OUTPUT_DIR / category
    cat_dir.mkdir(parents=True, exist_ok=True)
    prompt_file = cat_dir / f"{prompt_id}_prompt.md"
    with open(prompt_file, "w", encoding="utf-8") as f:
        f.write(f"# {prompt_id} — {prompt_title}\n\n")
        f.write(f"**Category:** {category}  \n")
        f.write(f"**Generated:** {datetime.now().isoformat()}  \n\n")
        f.write(f"**Target Files:**\n")
        for tf in target_files:
            f.write(f"- `{tf}`\n")
        f.write(f"\n---\n\n{full_prompt}\n")

    print(f"\n{'='*60}")
    print(f"▶ [{prompt_id}] {prompt_title}")
    print(f"  Files: {', '.join(target_files[:3])}")
    print(f"  └─ Prompt saved → {prompt_file}")

    return {
        "id": prompt_id,
        "title": prompt_title,
        "category": category,
        "status": "pending",
        "target_files": target_files,
        "prompt_file": str(prompt_file),
        "timestamp": datetime.now().isoformat(),
    }


def save_result(result: dict, output_dir: Path):
    cat_dir = output_dir / result["category"]
    cat_dir.mkdir(parents=True, exist_ok=True)
    fname = cat_dir / f"{result['id']}.md"
    with open(fname, "w", encoding="utf-8") as f:
        f.write(f"# {result['id']} — {result['title']}\n\n")
        f.write(f"**Category:** {result['category']}  \n")
        f.write(f"**Status:** {result['status']}  \n")
        f.write(f"**Timestamp:** {result['timestamp']}  \n\n")
        f.write(f"**Target Files:**\n")
        for tf in result["target_files"]:
            f.write(f"- `{tf}`\n")
        f.write(f"\n---\n\n## Output\n\n```\n{result.get('output', '(pending)')}\n```\n")
    return fname


def save_summary(results: list, output_dir: Path):
    summary_path = output_dir / "SUMMARY.md"
    total = len(results)
    pending = sum(1 for r in results if r["status"] == "pending")

    with open(summary_path, "w", encoding="utf-8") as f:
        f.write("# CRIUS COMPRESSOR — Audit Summary\n\n")
        f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}  \n")
        f.write(f"**Total:** {total} | ⏳ Pending: {pending}\n\n")
        f.write("| # | ID | Category | Title | Status | Prompt File |\n")
        f.write("|---|---|---|---|---|---|\n")
        for i, r in enumerate(results, 1):
            pf = Path(r.get('prompt_file', '')).name if r.get('prompt_file') else '-'
            f.write(f"| {i} | {r['id']} | {r['category']} | {r['title']} | ⏳ {r['status']} | `{pf}` |\n")
        f.write("\n---\n\n### Cara pakai\n\n")
        f.write("1. Minta OpenCode agent membaca setiap file `*_prompt.md`\n")
        f.write("2. Agent akan menulis hasil audit ke file `{ID}.md` di folder yang sama\n")
        f.write("3. Update status di file `results.json` setelah selesai\n")

    print(f"\n{'='*60}")
    print(f"📊 SUMMARY saved to: {summary_path}")
    print(f"   Total: {total} | ⏳ Pending: {pending}")
    return summary_path


def main():
    parser = argparse.ArgumentParser(description="CRIUS Audit Runner for DeepSeek via OpenCode")
    parser.add_argument("category", nargs="?", default="all",
                        choices=["all"] + list(PROMPTS.keys()),
                        help="Kategori audit (default: all)")
    parser.add_argument("--dry-run", action="store_true",
                        help="Tampilkan daftar prompt tanpa eksekusi")
    parser.add_argument("--start-from", type=int, default=0,
                        help="Skip N prompt pertama (untuk resume)")
    parser.add_argument("--delay", type=int, default=0,
                        help="Tidak dipakai lagi (dulu untuk delay antar prompt)")
    args = parser.parse_args()

    OUTPUT_DIR.mkdir(exist_ok=True)

    if args.category == "all":
        queue = [(cat, p) for cat, prompts in PROMPTS.items() for p in prompts]
    else:
        queue = [(args.category, p) for p in PROMPTS[args.category]]

    total = len(queue)
    print(f"\n🔍 CRIUS COMPRESSOR — DeepSeek Audit Runner")
    print(f"   Category: {args.category} | Total prompts: {total}")
    print(f"   Output: {OUTPUT_DIR.absolute()}")
    if args.dry_run:
        print(f"   MODE: DRY RUN\n")

    results = []
    for i, (cat, prompt_data) in enumerate(queue):
        if i < args.start_from:
            print(f"  ⏭ Skip [{i+1}/{total}] {prompt_data['id']}")
            continue

        print(f"\n[{i+1}/{total}]", end=" ")
        result = run_prompt(prompt_data, cat, dry_run=args.dry_run)
        results.append(result)

        if not args.dry_run:
            save_result(result, OUTPUT_DIR)

    if not args.dry_run:
        save_summary(results, OUTPUT_DIR)

    # Simpan raw JSON
    json_path = OUTPUT_DIR / "results.json"
    with open(json_path, "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)

    print(f"\n✅ Prompt files siap. Jalankan agent untuk memproses setiap file *_prompt.md")
    print(f"   Atau minta OpenCode membaca semua prompt dari: {OUTPUT_DIR.absolute()}\n")


if __name__ == "__main__":
    main()
