<?php
declare(strict_types=1);
/* ====================================================
SAFETY TALK SYSTEM - PT CITRA LAMPIA MANDIRI
File        : formulir/simpan.php
Versi       : 2.5.0
Revisi      : 001 - Support CSV 10 kolom (peserta_seharusnya)
Tanggal     : 20-02-2026
Fitur       : - ✅ Process new 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
              - ✅ Activity Logging + Rate Limiting
              
📋 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 semua input fields dengan pattern + length + range
  3. Validasi file upload: MIME type via finfo_file(), max size, sanitize filename
  4. Simpan 10 kolom ke CSV dengan fputcsv() yang aman
  5. Generate unique filename: 'ST_' . date('Ymd_His') . '_' . bin2hex(random_bytes(8)) . '.' . $ext
====================================================
*/

// ===== 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('[SAVE_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(
        "[SAVE_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: input_safety_talk.php');
    exit;
}

// 🚫 Rate Limiting (cegah spam/abuse)
if(isset($_SESSION['last_submit_time']) && (time() - $_SESSION['last_submit_time'] < $config->rateLimitSeconds)) {
    $remaining = $config->rateLimitSeconds - (time() - $_SESSION['last_submit_time']);
    $_SESSION['error_msg'] = "Silakan tunggu {$remaining} detik sebelum submit lagi.";
    header('Location: input_safety_talk.php');
    exit;
}
$_SESSION['last_submit_time'] = time();

// ===== HELPER: CSV INJECTION PREVENTION =====
/**
 * Sanitize cell value to prevent CSV formula injection
 * Dangerous prefixes: =, +, -, @, tab, newline, CR
 */
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 INPUT =====
$errors = [];

// 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: validasi dari list hari Indonesia
$hari = trim($_POST['hari'] ?? '');
$validHari = ['Senin','Selasa','Rabu','Kamis','Jumat','Sabtu','Minggu'];
if(!in_array($hari, $validHari)) {
    $errors[] = 'Hari tidak valid!';
}

// Departemen: fixed value
$dept = trim($_POST['departemen'] ?? '');
if($dept !== 'QHSEF') {
    $errors[] = 'Departemen tidak valid!';
}

// 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);
    $_SESSION['form_data'] = $_POST; // Preserve for re-fill
    header('Location: input_safety_talk.php');
    exit;
}

// ===== FILE UPLOAD HANDLING (Server-Side Validation) =====
$target_dir = __DIR__ . '/../uploads/';
$data_dir = __DIR__ . '/../data/';

// Ensure directories exist with proper permissions
foreach([$target_dir, $data_dir] as $dir) {
    if(!is_dir($dir) && !@mkdir($dir, 0755, true)) {
        error_log("[SAVE_PROCESS] Failed to create directory: $dir");
    }
}

// Add .htaccess to uploads folder if not exists
$htaccessPath = $target_dir . '.htaccess';
if(!file_exists($htaccessPath)) {
    @file_put_contents($htaccessPath, 
        "<FilesMatch \"\.(php|phtml|php3|php4|php5|pl|py|jsp|asp|sh|cgi)$\">\n    Deny from all\n</FilesMatch>\nphp_flag engine off\n");
}

$uploaded_filenames = [];

if(isset($_FILES['bukti']) && is_array($_FILES['bukti']['name'])) {
    $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 (CRITICAL)
        $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;
        
        // Generate unique filename with cryptographically secure random bytes
        $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 no files uploaded successfully, abort
if(empty($uploaded_filenames)) {
    $_SESSION['error_msg'] = 'Minimal 1 foto bukti wajib diupload!';
    header('Location: input_safety_talk.php');
    exit;
}

// ===== PREPARE CSV DATA (10 Columns) =====
$string_foto = implode('|', $uploaded_filenames);

// ✅ CSV Structure: 10 columns [0-9]
$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 TO CSV WITH ERROR HANDLING =====
$file_path = $data_dir . 'safety_talk.csv';
$file = @fopen($file_path, 'a');

if($file === false) {
    error_log("[SAVE_ERROR] Failed to open CSV file: {$file_path}");
    $_SESSION['error_msg'] = 'Gagal menyimpan data. Hubungi administrator.';
    
    // Clean up uploaded files on failure
    foreach($uploaded_filenames as $fname) {
        @unlink($target_dir . $fname);
    }
    
    header('Location: input_safety_talk.php');
    exit;
}

// Use file locking to prevent race conditions
if(flock($file, LOCK_EX)) {
    $result = fputcsv($file, $safeRow);
    flock($file, LOCK_UN);
    fclose($file);
    
    if($result !== false) {
        // ✅ Log successful submission
        logActivity(
            'SAVE_RECORD', 
            "Tanggal:{$tanggal}, Divisi:{$divisi}, Nama:{$nama}, Hadir:{$peserta}, Target:{$peserta_seharusnya}, Files:" . count($uploaded_filenames),
            $data_dir . 'activity_log.csv'
        );
        
        // ✅ Clear form data from session
        unset($_SESSION['form_data']);
        
        // ✅ Redirect with success message via session
        $_SESSION['success_msg'] = '✅ Data Safety Talk berhasil disimpan!';
        header('Location: ../index.php');
        exit;
    } else {
        error_log("[SAVE_ERROR] fputcsv failed for row: " . json_encode($safeRow));
    }
} else {
    error_log("[SAVE_ERROR] Failed to acquire file lock: {$file_path}");
}

// Fallback error handling
$_SESSION['error_msg'] = 'Gagal menyimpan data. Silakan coba lagi.';

// Clean up uploaded files on failure
foreach($uploaded_filenames as $fname) {
    @unlink($target_dir . $fname);
}

header('Location: input_safety_talk.php');
exit;