<?php
declare(strict_types=1);
/* ====================================================
SAFETY TALK SYSTEM - PT CITRA LAMPIA MANDIRI
File        : formulir/update_proses.php
Versi       : 2.5.0
Revisi      : 001 - Support CSV 10 kolom (peserta_seharusnya)
Tanggal     : 20-02-2026
Fitur       : - ✅ Process edit safety talk submission
              - ✅ CSV Structure: 10 columns [0-9]
              - ✅ Field "peserta_seharusnya" (index 8)
              - ✅ Authentication & CSRF Check
              - ✅ Input Validation & Sanitization
              - ✅ CSV Injection Prevention
              - ✅ Secure File Upload Handling
              - ✅ Old Photo Cleanup + Soft Delete
              - ✅ Activity Logging + Rate Limiting
              - ✅ File Locking for Data Integrity
              
📋 CSV Structure (10 columns):
  [0]tanggal, [1]hari, [2]departemen, [3]divisi, 
  [4]nama, [5]jabatan, [6]topik, [7]peserta, 
  [8]peserta_seharusnya, [9]foto
  
⚠️ SERVER-SIDE VALIDATION:
  1. Validasi CSRF token: hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])
  2. Validasi row_id: harus integer valid dan ada di CSV
  3. Validasi tanggal: format YYYY-MM-DD, tidak future date
  4. Validasi divisi: hanya HS/EF/QMS
  5. Validasi nama/jabatan/topik: sanitize & length check
  6. Validasi peserta & peserta_seharusnya: integer 1-999
  7. Validasi file upload: MIME type via finfo_file(), max size, sanitize filename
  8. Hapus foto lama dari folder uploads/ jika ada upload baru
  9. Update CSV dengan fputcsv() yang aman (10 kolom)
 10. Log aktivitas edit ke activity_log.csv
====================================================
*/

// ===== KONFIGURASI GLOBAL (Readonly Class - PHP 8.2+) =====
final readonly class AppConfig
{
    public function __construct(
        public string $appVersion = '2.5.0',
        public string $revision = '001',
        public string $timezone = 'Asia/Jakarta',
        public int $maxUploadFiles = 3,
        public int $maxFileSizeBytes = 3_145_728, // 3MB
        public array $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'],
        public array $allowedDivisi = ['HS', 'EF', 'QMS'],
        public int $rateLimitSeconds = 30,
        public int $sessionTimeout = 1800,
    ) {}
}

$config = new AppConfig();
date_default_timezone_set($config->timezone);

// ===== AUTH & SECURITY CHECK =====
if(session_status() === PHP_SESSION_NONE && !@session_start()) {
    error_log('[UPDATE_PROCESS] Failed to start session');
    exit('❌ Gagal memulai sesi. Hubungi administrator.');
}

// 🔐 Cek login - critical security fix
if(!isset($_SESSION['user_logged_in']) || $_SESSION['user_logged_in'] !== true) {
    error_log(sprintf(
        "[UPDATE_DENIED] IP:%s Time:%s - Unauthenticated POST attempt\n",
        $_SERVER['REMOTE_ADDR'] ?? 'unknown',
        date('c')
    ));
    header('Location: ../login.php?access_denied=1');
    exit;
}

// Session timeout check
if(isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > $config->sessionTimeout)) {
    session_unset();
    session_destroy();
    header('Location: ../login.php?timeout=1');
    exit;
}
$_SESSION['last_activity'] = time();

// 🔐 CSRF Token Validation
if(!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
    error_log(sprintf(
        "[CSRF_FAIL] IP:%s User:%s Time:%s\n",
        $_SERVER['REMOTE_ADDR'] ?? 'unknown',
        $_SESSION['user_name'] ?? 'unknown',
        date('c')
    ));
    $_SESSION['error_msg'] = 'Token keamanan tidak valid!';
    header('Location: edit_safety_talk.php?id=' . (int)($_POST['row_id'] ?? 0));
    exit;
}

// 🚫 Rate Limiting (cegah spam/abuse)
if(isset($_SESSION['last_update_time']) && (time() - $_SESSION['last_update_time'] < $config->rateLimitSeconds)) {
    $remaining = $config->rateLimitSeconds - (time() - $_SESSION['last_update_time']);
    $_SESSION['error_msg'] = "Silakan tunggu {$remaining} detik sebelum update lagi.";
    header('Location: edit_safety_talk.php?id=' . (int)($_POST['row_id'] ?? 0));
    exit;
}
$_SESSION['last_update_time'] = time();

// ===== HELPER: CSV INJECTION PREVENTION =====
/**
 * Sanitize cell value to prevent CSV formula injection
 */
function sanitizeForCSV(mixed $value): string
{
    $value = (string)$value;
    if(preg_match('/^[=+\-@\t\r\n]/', $value)) {
        return "'" . $value;
    }
    return str_replace('"', '""', $value);
}

function sanitizeCSVRow(array $row): array
{
    return array_map('sanitizeForCSV', $row);
}

// ===== HELPER: LOG ACTIVITY =====
function logActivity(string $action, string $details, string $logPath): void
{
    $header = ['timestamp', 'user', 'ip', 'action', 'details'];
    $logDir = dirname($logPath);
    
    if(!is_dir($logDir) && !@mkdir($logDir, 0755, true)) {
        return;
    }
    
    $file = @fopen($logPath, 'a');
    if($file === false) return;
    
    $exists = filesize($logPath) > 0;
    if(!$exists) @fputcsv($file, $header);
    
    @fputcsv($file, [
        date('Y-m-d H:i:s'),
        $_SESSION['user_name'] ?? 'unknown',
        $_SERVER['REMOTE_ADDR'] ?? 'unknown',
        $action,
        $details
    ]);
    fclose($file);
}

// ===== GET & VALIDATE ROW ID =====
$row_to_edit = filter_var($_POST['row_id'] ?? -1, FILTER_VALIDATE_INT);
if($row_to_edit === false || $row_to_edit < 0) {
    $_SESSION['error_msg'] = 'ID data tidak valid!';
    header('Location: ../rekap.php');
    exit;
}

// ===== GET & VALIDATE INPUT =====
$errors = [];
$target_dir = __DIR__ . '/../uploads/';
$data_dir = __DIR__ . '/../data/';

// Tanggal: validasi format & tidak future date
$tanggal = trim($_POST['tanggal'] ?? '');
if(!preg_match('/^\d{4}-\d{2}-\d{2}$/', $tanggal)) {
    $errors[] = 'Format tanggal tidak valid!';
} elseif(strtotime($tanggal) > time()) {
    $errors[] = 'Tanggal tidak boleh melebihi hari ini!';
}

// Hari: auto-calculate from tanggal (ignore user input for security)
$dayName = date('l', strtotime($tanggal));
$hariMap = [
    'Sunday'=>'Minggu','Monday'=>'Senin','Tuesday'=>'Selasa','Wednesday'=>'Rabu',
    'Thursday'=>'Kamis','Friday'=>'Jumat','Saturday'=>'Sabtu'
];
$hari = $hariMap[$dayName] ?? '';

// Departemen: fixed value
$dept = 'QHSEF';

// Divisi: whitelist validation
$divisi = trim($_POST['divisi'] ?? '');
if(!in_array($divisi, $config->allowedDivisi)) {
    $errors[] = 'Divisi tidak valid!';
}

// Nama PIC: sanitize + length + pattern
$nama = trim($_POST['nama'] ?? '');
if(strlen($nama) < 3 || strlen($nama) > 50 || !preg_match('/^[A-Za-z\s\.\'\-]{3,50}$/', $nama)) {
    $errors[] = 'Nama PIC tidak valid (3-50 karakter)!';
}

// Jabatan: sanitize + length
$jabatan = trim($_POST['jabatan'] ?? '');
if(strlen($jabatan) < 2 || strlen($jabatan) > 50 || !preg_match('/^[A-Za-z\s\.\,\-\/]{2,50}$/', $jabatan)) {
    $errors[] = 'Jabatan tidak valid (2-50 karakter)!';
}

// Topik: sanitize + length
$topik = trim($_POST['topik'] ?? '');
if(strlen($topik) < 10 || strlen($topik) > 200) {
    $errors[] = 'Topik materi harus 10-200 karakter!';
}

// Peserta Hadir: integer range validation
$peserta = filter_var($_POST['peserta'] ?? 0, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 999]]);
if($peserta === false || $peserta < 1 || $peserta > 999) {
    $errors[] = 'Jumlah peserta hadir harus antara 1-999!';
}

// ✅ NEW: Peserta Seharusnya (opsional, default = peserta jika kosong)
$peserta_seharusnya_raw = trim($_POST['peserta_seharusnya'] ?? '');
if(!empty($peserta_seharusnya_raw)) {
    $peserta_seharusnya = filter_var($peserta_seharusnya_raw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 999]]);
    if($peserta_seharusnya === false || $peserta_seharusnya < 1 || $peserta_seharusnya > 999) {
        $errors[] = 'Peserta seharusnya harus antara 1-999!';
    }
} else {
    // Auto-fill: jika kosong, sama dengan peserta hadir
    $peserta_seharusnya = $peserta;
}

// Jika ada error, redirect dengan pesan
if(!empty($errors)) {
    $_SESSION['error_msg'] = implode(' ', $errors);
    header('Location: edit_safety_talk.php?id=' . $row_to_edit);
    exit;
}

// ===== LOAD EXISTING CSV DATA WITH FILE LOCKING =====
$csvPath = $data_dir . 'safety_talk.csv';

if(!file_exists($csvPath) || !is_readable($csvPath)) {
    $_SESSION['error_msg'] = 'File data tidak ditemukan!';
    header('Location: ../rekap.php');
    exit;
}

// Read CSV with shared lock to prevent race conditions
$handle = @fopen($csvPath, 'r');
if($handle === false || !@flock($handle, LOCK_SH)) {
    error_log("[UPDATE_ERROR] Failed to lock/read CSV: {$csvPath}");
    $_SESSION['error_msg'] = 'Gagal membaca data. Silakan coba lagi.';
    header('Location: ../rekap.php');
    exit;
}

$rows = [];
$currentIndex = 0;
$originalRow = null;

while(($data = @fgetcsv($handle, 1000, ",")) !== false) {
    // ✅ Validasi minimal 9 kolom (data lama) atau 10 kolom (data baru)
    if(!is_array($data) || count($data) < 9) {
        $currentIndex++;
        continue;
    }
    
    if($currentIndex == $row_to_edit) {
        $originalRow = $data;
    }
    $rows[] = $data;
    $currentIndex++;
}
@flock($handle, LOCK_UN);
@fclose($handle);

// Validate row exists
if($originalRow === null || !isset($rows[$row_to_edit])) {
    $_SESSION['error_msg'] = 'Data tidak ditemukan!';
    header('Location: ../rekap.php');
    exit;
}

// ===== DETERMINE COLUMN INDICES (Backward Compatible) =====
// Foto ada di index 9 untuk data baru (10 kolom), atau index 8 untuk data lama (9 kolom)
$fotoIndex = count($originalRow) >= 10 ? 9 : 8;
$fotoLama = !empty($originalRow[$fotoIndex]) ? array_filter(explode("|", $originalRow[$fotoIndex])) : [];

// ✅ Backward compatible: jika kolom 8 belum ada, anggap sama dengan peserta
$original_peserta_seharusnya = isset($originalRow[8]) ? (int)$originalRow[8] : $peserta;

// ===== PROCESS FILE UPLOADS (Server-Side Validation) =====
$uploaded_filenames = [];
$filesToDelete = [];

// Process new uploads
if(isset($_FILES['bukti']) && is_array($_FILES['bukti']['name']) && !empty($_FILES['bukti']['name'][0])) {
    $file_count = min(count($_FILES['bukti']['name']), $config->maxUploadFiles);
    
    for($i = 0; $i < $file_count; $i++) {
        if(empty($_FILES['bukti']['name'][$i])) continue;
        if($_FILES['bukti']['error'][$i] !== UPLOAD_ERR_OK) continue;
        
        // ✅ Server-side MIME type validation
        $tmpName = $_FILES['bukti']['tmp_name'][$i];
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = finfo_file($finfo, $tmpName);
        finfo_close($finfo);
        
        if(!in_array($mime, $config->allowedMimeTypes)) continue;
        
        // ✅ Server-side file size validation
        if($_FILES['bukti']['size'][$i] > $config->maxFileSizeBytes) continue;
        
        // ✅ Sanitize & generate secure filename
        $originalName = basename($_FILES['bukti']['name'][$i]);
        $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        if(!in_array($ext, ['jpg', 'jpeg', 'png', 'webp'])) continue;
        
        $nama_baru = 'ST_' . date('Ymd_His') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
        
        if(@move_uploaded_file($tmpName, $target_dir . $nama_baru)) {
            @chmod($target_dir . $nama_baru, 0644);
            $uploaded_filenames[] = $nama_baru;
        }
    }
    
    // If new photos uploaded, they REPLACE old ones
    if(!empty($uploaded_filenames)) {
        // Mark old photos for deletion
        if(!empty($fotoLama)) {
            $filesToDelete = $fotoLama;
        }
        $string_foto = implode('|', $uploaded_filenames);
    } else {
        // Keep old photos if upload failed/empty
        $string_foto = $originalRow[$fotoIndex] ?? '';
    }
} else {
    // No new uploads - keep old photos
    $string_foto = $originalRow[$fotoIndex] ?? '';
}

// ===== PROCESS SOFT DELETE (foto_dihapus field from form) =====
$foto_dihapus = $_POST['foto_dihapus'] ?? '';
if(!empty($foto_dihapus) && empty($uploaded_filenames)) {
    // User marked specific photos for deletion (no new uploads)
    $existing_photos = array_filter(explode('|', $string_foto));
    $photos_to_remove = array_filter(explode(',', $foto_dihapus));
    
    $remaining_photos = array_filter($existing_photos, fn($photo): bool => !in_array($photo, $photos_to_remove));
    
    // Mark removed photos for filesystem cleanup
    $filesToDelete = array_merge($filesToDelete, array_diff($existing_photos, $remaining_photos));
    
    $string_foto = implode('|', $remaining_photos);
}

// ===== PREPARE UPDATED ROW (10 Columns) =====
$rawRow = [
    $tanggal,              // [0] tanggal
    $hari,                 // [1] hari
    $dept,                 // [2] departemen
    $divisi,               // [3] divisi
    $nama,                 // [4] nama PIC
    $jabatan,              // [5] jabatan
    $topik,                // [6] topik
    $peserta,              // [7] peserta hadir
    $peserta_seharusnya,   // [8] peserta seharusnya (NEW) ⭐
    $string_foto           // [9] foto
];

// ✅ Sanitize for CSV injection prevention
$safeRow = sanitizeCSVRow($rawRow);

// ===== WRITE UPDATED CSV WITH FILE LOCKING =====
$handle = @fopen($csvPath, 'w');
if($handle === false || !@flock($handle, LOCK_EX)) {
    error_log("[UPDATE_ERROR] Failed to lock/write CSV: {$csvPath}");
    
    // Cleanup uploaded files on failure
    foreach($uploaded_filenames as $fname) {
        @unlink($target_dir . $fname);
    }
    
    $_SESSION['error_msg'] = 'Gagal menyimpan perubahan. Silakan coba lagi.';
    header('Location: edit_safety_talk.php?id=' . $row_to_edit);
    exit;
}

foreach($rows as $index => $row) {
    if($index == $row_to_edit) {
        @fputcsv($handle, $safeRow);
    } else {
        @fputcsv($handle, $row);
    }
}
@flock($handle, LOCK_UN);
@fclose($handle);

// ===== CLEANUP OLD PHOTOS FROM FILESYSTEM =====
foreach(array_unique($filesToDelete) as $oldPhoto) {
    $cleanName = preg_replace('/[^a-zA-Z0-9_\.\-]/', '', basename($oldPhoto));
    if(!empty($cleanName) && file_exists($target_dir . $cleanName)) {
        @unlink($target_dir . $cleanName);
    }
}

// ===== LOG SUCCESS & REDIRECT =====
logActivity(
    'UPDATE_RECORD', 
    "RowID:{$row_to_edit}, Tanggal:{$tanggal}, Divisi:{$divisi}, Nama:{$nama}, Hadir:{$peserta}, Target:{$peserta_seharusnya}",
    $data_dir . 'activity_log.csv'
);

$_SESSION['success_msg'] = '✅ Data Safety Talk berhasil diperbarui!';
header('Location: ../rekap.php');
exit;