<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Student;
use App\Models\StudentValue;
use App\Models\DistanceCalculation;
use App\Models\WeightCalculation;
use App\Models\WeightRatio;
use App\Models\Prediction;
use App\Models\GraduationRule;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Illuminate\Support\Facades\Log;

class PrediksiController extends Controller
{
    // ==================== PARAMETER & KONSTANTA ====================
    private const FUZZY_STRENGTH = 2.0; // m parameter untuk fuzzy strength
    private const EPSILON = 0.001; // Small value to avoid division by zero
    private const FEATURE_WEIGHTS = [
        'avg_semester_score' => 0.4,
        'usp_score' => 0.3,
        'kerapian' => 0.1,
        'kerajinan' => 0.1,
        'sikap' => 0.1
    ];

    // ==================== ROUTE HANDLERS ====================
    public function index()
    {
        return view('pages.prediksi');
    }

    public function showResult($id)
    {
        Log::info("=== MENAMPILKAN HASIL PREDIKSI ===", ['student_id' => $id]);

        $testStudent = Student::with('studentValues')->findOrFail($id);

        Log::info("Data siswa yang ditemukan", [
            'student_id' => $testStudent->id,
            'nisn' => $testStudent->nisn,
            'name' => $testStudent->name,
            'student_values_count' => $testStudent->studentValues->count()
        ]);

        // Ambil data hasil distance dan weight yang sudah dihitung sebelumnya
        $distanceData = DistanceCalculation::with(['trainingStudent', 'weightCalculation'])
            ->where('test_student_id', $testStudent->id)
            ->get();

        Log::info("Data perhitungan jarak yang ditemukan", [
            'distance_calculations_count' => $distanceData->count()
        ]);

        $results = $distanceData->map(function ($data) {
            return [
                'nisn' => $data->trainingStudent->nisn ?? '-',
                'nama' => $data->trainingStudent->name ?? '-',
                'distance' => $data->distance,
                'weight' => optional($data->weightCalculation)->weight,
                'true_status' => optional($data->trainingStudent)->true_status ?? '-'
            ];
        });

        Log::info("Hasil prediksi yang akan ditampilkan", [
            'results_count' => $results->count(),
            'results_summary' => $results->take(3)->toArray() // Ambil 3 hasil pertama untuk log
        ]);

        // Ambil prediksi final
        $prediction = Prediction::where('test_student_id', $testStudent->id)->first();
        $predictedStatus = $prediction ? $prediction->predicted_status : null;
        if ($prediction) {
            Log::info("Prediksi final", [
                'predicted_status' => $prediction->predicted_status,
                'k_value' => $prediction->k_value
            ]);
        }

        Log::info("=== SELESAI MENAMPILKAN HASIL PREDIKSI ===");

        return view('pages.prediksi', compact('testStudent', 'results', 'predictedStatus'));
    }

    public function processAndPredict(Request $request)
    {
        Log::info("=== MULAI PROSES PREDIKSI MANUAL ===");

        $data = $request->input('data');
        $k = $request->input('k_value', 5);  // Default K value to 5 if not provided

        Log::info("Data input yang diterima", [
            'data' => $data,
            'k_value' => $k
        ]);

        if (!$data || !is_array($data)) {
            Log::error("Input data tidak valid", ['data' => $data]);
            return back()->with('error', 'Input data tidak valid.');
        }

        // Extract name and NISN
        $name = $data['nama'] ?? '';
        $nisn = $data['nisn'] ?? '';

        Log::info("Data siswa yang akan diproses", [
            'name' => $name,
            'nisn' => $nisn
        ]);

        if (empty($name) || empty($nisn)) {
            Log::error("Nama atau NISN kosong", ['name' => $name, 'nisn' => $nisn]);
            return back()->with('error', 'Nama dan NISN harus diisi.');
        }

        Log::info("Membuat record siswa baru");
        $testStudent = Student::create([
            'nisn' => $nisn,
            'name' => $name,
            'jenis_data' => 'testing',
            'true_status' => null
        ]);

        Log::info("Siswa berhasil dibuat", [
            'student_id' => $testStudent->id,
            'nisn' => $testStudent->nisn,
            'name' => $testStudent->name
        ]);

        // Remove name and NISN from data before creating student values
        unset($data['nama'], $data['nisn']);

        Log::info("Menyimpan nilai-nilai siswa");
        // Create student values for remaining data
        foreach ($data as $key => $value) {
            if (trim($key) !== '' && $value !== null) {
                StudentValue::create([
                    'student_id' => $testStudent->id,
                    'key' => $key,
                    'value' => $value
                ]);

                Log::info("Nilai siswa disimpan", [
                    'student_id' => $testStudent->id,
                    'key' => $key,
                    'value' => $value
                ]);
            }
        }

        Log::info("Memulai proses prediksi untuk siswa", ['student_id' => $testStudent->id]);
        $this->predictForStudent($testStudent, $k);

        // Ambil hasil prediksi manual
        $prediction = Prediction::where('test_student_id', $testStudent->id)->first();
        $neighbors = \App\Models\DistanceCalculation::with(['trainingStudent', 'weightCalculation'])
            ->where('test_student_id', $testStudent->id)
            ->orderBy('distance')
            ->limit($k)
            ->get()
            ->map(function ($item) {
                return [
                    'nisn' => $item->trainingStudent->nisn ?? '-',
                    'nama' => $item->trainingStudent->name ?? '-',
                    'true_status' => $item->trainingStudent->true_status ?? '-',
                    'distance' => $item->distance,
                    'weight' => optional($item->weightCalculation)->weight,
                ];
            })->toArray();
        $ratios = \App\Models\WeightRatio::where('test_student_id', $testStudent->id)->get(['class', 'total_weight', 'weight_ratio']);
        $manualPrediction = [
            'nisn' => $testStudent->nisn,
            'name' => $testStudent->name,
            'status' => $prediction ? $prediction->predicted_status : '-',
            'neighbors' => $neighbors,
            'ratios' => $ratios
        ];
        $studentValues = $testStudent->studentValues->pluck('value', 'key')->toArray();
        // Hitung rata-rata nilai akademik (semester_1 sampai semester_6)
        $akademik = collect(range(1,6))->map(function($i) use ($studentValues) {
            return isset($studentValues['semester_'.$i]) ? floatval($studentValues['semester_'.$i]) : null;
        })->filter()->avg();
        // Normalisasi ke 0-1 (anggap skala 0-100)
        $akademik_normalized = $akademik !== null ? round($akademik / 100, 4) : 0;
        return view('pages.prediksi', [
            'manualPrediction' => $manualPrediction,
            'studentValues' => $studentValues,
            'activeInputMethod' => 'manual',
            'akademik_normalized' => $akademik_normalized
        ])->with('success', 'Prediksi berhasil dilakukan.');
    }

    // ==================== FUNGSI PENENTUAN STATUS KELULUSAN ====================
    private function tentukanStatusKelulusan($fitur)
    {
        Log::info("=== MULAI PENENTUAN STATUS KELULUSAN ===", ['fitur' => $fitur]);

        // Ambil semua aturan kelulusan dan urutkan berdasarkan priority
        $rules = GraduationRule::orderBy('priority')->get();
        Log::info("Aturan kelulusan yang ditemukan", ['rules_count' => $rules->count()]);

        foreach ($rules as $rule) {
            $attribute = $rule->attribute;
            $operator = $rule->operator;
            $value = $rule->value;
            $category = $rule->category;

            Log::info("Mengevaluasi aturan", [
                'attribute' => $attribute,
                'operator' => $operator,
                'value' => $value,
                'category' => $category
            ]);

            // Cek apakah atribut ada dalam fitur
            if (!isset($fitur[$attribute])) {
                Log::info("Atribut tidak ditemukan dalam fitur", ['attribute' => $attribute]);
                continue;
            }

            $fiturValue = $this->convertToNumeric($fitur[$attribute]);
            $ruleValue = floatval($value);

            Log::info("Nilai fitur vs aturan", [
                'fitur_value' => $fiturValue,
                'rule_value' => $ruleValue,
                'operator' => $operator
            ]);

            // Evaluasi aturan berdasarkan operator
            $isRuleMet = match ($operator) {
                '=' => $fiturValue == $ruleValue,
                '>=' => $fiturValue >= $ruleValue,
                '<=' => $fiturValue <= $ruleValue,
                '>' => $fiturValue > $ruleValue,
                '<' => $fiturValue < $ruleValue,
                default => false
            };

            Log::info("Hasil evaluasi aturan", [
                'is_rule_met' => $isRuleMet,
                'category' => $category
            ]);

            if ($isRuleMet) {
                Log::info("=== ATURAN KELULUSAN DITEMUKAN ===", ['category' => $category]);
                return $category;
            }
        }

        // Jika tidak ada aturan yang terpenuhi, gunakan hasil Fuzzy KNN
        Log::info("=== TIDAK ADA ATURAN KELULUSAN YANG TERPENUHI ===");
        return null;
    }

    // ==================== FUNGSI UTAMA FUZZY KNN ====================
    private function predictForStudent($testStudent, $k = 5)
    {
        Log::info("=== MULAI PREDIKSI UNTUK SISWA ===", [
            'student_id' => $testStudent->id,
            'nisn' => $testStudent->nisn,
            'name' => $testStudent->name,
            'k_value' => $k
        ]);

        // 1. Persiapan Data
        Log::info("1. Persiapan Data - Mencari MinMax dari data training");
        $minMax = $this->getMinMaxPerFeatureFromTraining();
        if (empty($minMax)) {
            Log::error('MinMax data kosong, pastikan data latih sudah ada.');
            return;
        }
        Log::info("MinMax data berhasil diperoleh", ['minMax' => $minMax]);

        // 2. Normalisasi Data Testing
        Log::info("2. Normalisasi Data Testing");
        $testValues = $testStudent->studentValues->pluck('value', 'key');
        Log::info("Data testing sebelum normalisasi", ['testValues' => $testValues->toArray()]);

        $testValues = $this->processAndNormalizeData($testValues, $minMax);
        Log::info("Data testing setelah normalisasi", ['normalizedTestValues' => $testValues]);

        // 3. Ambil Data Training
        Log::info("3. Mengambil Data Training");
        $trainingStudents = Student::where('jenis_data', 'training')
            ->whereNotNull('true_status')
            ->where('id', '!=', $testStudent->id)
            ->with('studentValues')
            ->get();

        Log::info("Jumlah data training yang ditemukan", ['count' => $trainingStudents->count()]);

        // 4. Hitung Jarak ke Setiap Data Training
        Log::info("4. Menghitung Jarak ke Setiap Data Training");
        $distances = [];
        foreach ($trainingStudents as $train) {
            $trainValues = $train->studentValues->pluck('value', 'key');
            $trainValues = $this->processAndNormalizeData($trainValues, $minMax);

            $distance = $this->calculateFeatureWeightedDistance($testValues, $trainValues);
            $distances[] = [
                'student' => $train,
                'distance' => $distance
            ];

            Log::info("Jarak ke training student", [
                'training_id' => $train->id,
                'training_nisn' => $train->nisn,
                'training_name' => $train->name,
                'distance' => $distance,
                'true_status' => $train->true_status
            ]);
        }

        // 5. Ambil K Tetangga Terdekat
        Log::info("5. Mengambil K Tetangga Terdekat", ['k' => $k]);
        $neighbors = collect($distances)->sortBy('distance')->take($k);

        Log::info("K tetangga terdekat", [
            'neighbors' => $neighbors->map(function ($item) {
                return [
                    'id' => $item['student']->id,
                    'nisn' => $item['student']->nisn,
                    'name' => $item['student']->name,
                    'distance' => $item['distance'],
                    'true_status' => $item['student']->true_status
                ];
            })->toArray()
        ]);

        // 6. Inisialisasi Bobot Kelas
        Log::info("6. Inisialisasi Bobot Kelas");
        $classWeights = [
            'lulus' => 0,
            'lulus bersyarat' => 0,
            'tidak lulus' => 0,
        ];
        Log::info("Bobot kelas awal", $classWeights);

        // 7. Hitung Bobot Fuzzy untuk Setiap Tetangga
        Log::info("7. Menghitung Bobot Fuzzy untuk Setiap Tetangga");
        foreach ($neighbors as $item) {
            $train = $item['student'];
            $distance = $item['distance'];

            // Simpan Perhitungan Jarak
            $distanceCalc = DistanceCalculation::create([
                'test_student_id' => $testStudent->id,
                'training_data_id' => $train->id,
                'distance' => $distance
            ]);

            // Hitung Bobot Fuzzy
            $weight = $this->calculateFuzzyWeight($distance);
            Log::info("Perhitungan bobot fuzzy", [
                'training_id' => $train->id,
                'training_nisn' => $train->nisn,
                'distance' => $distance,
                'fuzzy_weight' => $weight,
                'true_status' => $train->true_status
            ]);

            // Simpan Perhitungan Bobot
            WeightCalculation::create([
                'distance_calculation_id' => $distanceCalc->id,
                'weight' => $weight
            ]);

            // Akumulasi Bobot untuk Setiap Kelas
            $status = $train->true_status;
            if ($status && isset($classWeights[$status])) {
                $classWeights[$status] += $weight;
                Log::info("Akumulasi bobot untuk kelas", [
                    'status' => $status,
                    'weight_added' => $weight,
                    'total_weight' => $classWeights[$status]
                ]);
            }
        }

        // 8. Hitung Total Bobot
        Log::info("8. Menghitung Total Bobot");
        $totalWeight = array_sum($classWeights);
        Log::info("Total bobot semua kelas", ['totalWeight' => $totalWeight]);

        // 9. Simpan Rasio Bobot untuk Setiap Kelas
        Log::info("9. Menyimpan Rasio Bobot untuk Setiap Kelas");
        WeightRatio::where('test_student_id', $testStudent->id)->delete();
        foreach ($classWeights as $status => $weight) {
            $weightRatio = $totalWeight > 0 ? $weight / $totalWeight : 0;
            WeightRatio::create([
                'test_student_id' => $testStudent->id,
                'class' => $status,
                'total_weight' => $weight,
                'weight_ratio' => $weightRatio,
            ]);

            Log::info("Rasio bobot untuk kelas", [
                'status' => $status,
                'total_weight' => $weight,
                'weight_ratio' => $weightRatio
            ]);
        }

        // 10. Tentukan Kelas dengan Bobot Tertinggi (Fuzzy KNN)
        Log::info("10. Menentukan Kelas dengan Bobot Tertinggi (Fuzzy KNN)");
        $fuzzyKnnStatus = collect($classWeights)->sortDesc()->keys()->first();
        Log::info("Hasil Fuzzy KNN", [
            'fuzzyKnnStatus' => $fuzzyKnnStatus,
            'classWeights' => $classWeights
        ]);

        // 11. Cek Aturan Kelulusan
        Log::info("11. Mengecek Aturan Kelulusan");
        $fitur = $testStudent->studentValues->pluck('value', 'key')->toArray();
        Log::info("Fitur untuk pengecekan aturan", ['fitur' => $fitur]);

        $ruleBasedStatus = $this->tentukanStatusKelulusan($fitur);
        Log::info("Hasil pengecekan aturan kelulusan", ['ruleBasedStatus' => $ruleBasedStatus]);

        // 12. Simpan Hasil Prediksi (Prioritaskan aturan kelulusan jika ada)
        $finalStatus = $ruleBasedStatus ?? $fuzzyKnnStatus;
        Log::info("12. Status Final Prediksi", [
            'ruleBasedStatus' => $ruleBasedStatus,
            'fuzzyKnnStatus' => $fuzzyKnnStatus,
            'finalStatus' => $finalStatus
        ]);

        Prediction::create([
            'test_student_id' => $testStudent->id,
            'predicted_status' => $finalStatus,
            'k_value' => $k
        ]);

        Log::info("=== SELESAI PREDIKSI UNTUK SISWA ===", [
            'student_id' => $testStudent->id,
            'nisn' => $testStudent->nisn,
            'final_prediction' => $finalStatus,
            'k_value' => $k
        ]);
    }

    // ==================== FUNGSI PENDUKUNG ====================
    private function normalize($value, $min, $max)
    {
        if ($value === null) {
            Log::info("Nilai null, return 0");
            return 0;
        }

        $value = floatval($value);
        $min = floatval($min);
        $max = floatval($max);

        if ($max - $min == 0) {
            Log::info("Min dan max sama, return 0 untuk menghindari pembagian nol", [
                'value' => $value,
                'min' => $min,
                'max' => $max
            ]);
            return 0; // Hindari pembagian dengan nol
        }

        // Normalisasi dan batasi hasil antara 0 dan 1
        $normalized = ($value - $min) / ($max - $min);
        $result = max(0, min(1, $normalized));

        Log::info("Normalisasi nilai", [
            'original_value' => $value,
            'min' => $min,
            'max' => $max,
            'normalized' => $normalized,
            'final_result' => $result
        ]);

        return $result;
    }

    private function processAndNormalizeData($studentValues, $minMax)
    {
        Log::info("=== MULAI PROSES NORMALISASI DATA ===");
        Log::info("Data yang akan dinormalisasi", ['studentValues' => $studentValues->toArray()]);
        Log::info("MinMax yang digunakan", ['minMax' => $minMax]);

        $normalizedValues = [];

        foreach ($studentValues as $key => $value) {
            $val = $this->convertToNumeric($value);

            if (isset($minMax[$key])) {
                $min = $minMax[$key]['min'];
                $max = $minMax[$key]['max'];
                $normalizedValues[$key] = $this->normalize($val, $min, $max);

                Log::info("Normalisasi fitur", [
                    'feature' => $key,
                    'original_value' => $value,
                    'numeric_value' => $val,
                    'min' => $min,
                    'max' => $max,
                    'normalized_value' => $normalizedValues[$key]
                ]);
            } else {
                // Jika min/max tidak ditemukan, gunakan bobot default
                $normalizedValues[$key] = 0;
                Log::warning("Feature {$key} tidak memiliki min/max values, menggunakan 0");
            }
        }

        Log::info("=== SELESAI PROSES NORMALISASI DATA ===", ['normalizedValues' => $normalizedValues]);
        return $normalizedValues;
    }

    private function getMinMaxPerFeatureFromTraining()
    {
        Log::info("=== MULAI MENCARI MINMAX DARI DATA TRAINING ===");

        // Ambil semua atribut dari data siswa uji
        $testStudent = Student::where('jenis_data', 'testing')
            ->latest()
            ->first();

        if (!$testStudent) {
            Log::error('Tidak ada data siswa uji yang ditemukan.');
            return [];
        }

        Log::info("Siswa uji yang digunakan untuk referensi", [
            'student_id' => $testStudent->id,
            'nisn' => $testStudent->nisn,
            'name' => $testStudent->name
        ]);

        $testAttributes = $testStudent->studentValues->pluck('key')->unique()->toArray();
        Log::info("Atribut yang akan dicari minmax", ['attributes' => $testAttributes]);

        $minMax = [];

        // Ambil data latih
        $dataLatih = Student::where('jenis_data', 'training')
            ->whereNotNull('true_status')
            ->with('studentValues')
            ->get();

        Log::info("Data latih yang ditemukan", [
            'training_count' => $dataLatih->count()
        ]);

        foreach ($testAttributes as $attribute) {
            Log::info("Mencari minmax untuk atribut", ['attribute' => $attribute]);

            if ($dataLatih->isNotEmpty()) {
                // Jika ada data latih, gunakan min dan max dari data latih
                $values = $dataLatih->flatMap(function ($student) use ($attribute) {
                    return $student->studentValues
                        ->where('key', $attribute)
                        ->pluck('value')
                        ->map(function ($value) {
                            return $this->convertToNumeric($value);
                        });
                })->filter()->values();

                if ($values->isNotEmpty()) {
                    $min = $values->min();
                    $max = $values->max();
                    $minMax[$attribute] = [
                        'min' => $min,
                        'max' => $max
                    ];

                    Log::info("Minmax dari data latih", [
                        'attribute' => $attribute,
                        'min' => $min,
                        'max' => $max,
                        'values_count' => $values->count()
                    ]);
                } else {
                    Log::warning("Tidak ada nilai untuk atribut dalam data latih", ['attribute' => $attribute]);
                }
            } else {
                // Jika tidak ada data latih, gunakan nilai default berdasarkan atribut
                Log::info("Tidak ada data latih, menggunakan nilai default untuk atribut", ['attribute' => $attribute]);

                switch ($attribute) {
                    case 'rata_rata':
                    case 'usp':
                        $minMax[$attribute] = ['min' => 0, 'max' => 100];
                        break;
                    case 'sikap':
                    case 'kerajinan':
                    case 'kerapian':
                        $minMax[$attribute] = ['min' => 0, 'max' => 1];
                        break;
                    default:
                        $minMax[$attribute] = ['min' => 0, 'max' => 100];
                }

                Log::info("Nilai default yang digunakan", [
                    'attribute' => $attribute,
                    'minmax' => $minMax[$attribute]
                ]);
            }
        }

        Log::info("=== SELESAI MENCARI MINMAX DARI DATA TRAINING ===", ['minMax' => $minMax]);
        return $minMax;
    }

    private function calculateFuzzyMembership($distance, $m = self::FUZZY_STRENGTH)
    {
        // Fungsi keanggotaan fuzzy menggunakan Euclidean distance
        // μ(x) = 1 / (1 + ε + d²)
        // Dimana d adalah jarak Euclidean dan ε adalah nilai kecil untuk menghindari pembagian nol
        $adjustedDistance = $distance + self::EPSILON;
        $membership = 1 / (1 + pow($adjustedDistance, 2));

        Log::info("Perhitungan Fuzzy Membership", [
            'distance' => $distance,
            'epsilon' => self::EPSILON,
            'adjusted_distance' => $adjustedDistance,
            'membership' => $membership
        ]);

        return $membership;
    }

    private function calculateFuzzyWeight($distance, $m = self::FUZZY_STRENGTH)
    {
        // Perhitungan bobot fuzzy menggunakan Euclidean distance
        // w = 1 / (1 + ε + d²)
        $weight = $this->calculateFuzzyMembership($distance, $m);

        Log::info("Perhitungan Fuzzy Weight", [
            'distance' => $distance,
            'fuzzy_weight' => $weight
        ]);

        return $weight;
    }

    private function calculateFeatureWeightedDistance($testValues, $trainValues)
    {
        Log::info("=== MULAI PERHITUNGAN JARAK BERBOBOT ===");
        Log::info("Data test", ['testValues' => $testValues]);
        Log::info("Data training", ['trainValues' => $trainValues]);

        $sum = 0;
        $totalWeight = 0;
        $featureDetails = [];

        foreach ($testValues as $key => $value) {
            if (isset($trainValues[$key])) {
                $testVal = $this->convertToNumeric($value);
                $trainVal = $this->convertToNumeric($trainValues[$key]);

                // Pembobotan fitur sesuai kaidah
                $weight = self::FEATURE_WEIGHTS[$key] ?? 0.1;
                $squaredDiff = pow($testVal - $trainVal, 2);
                $weightedDiff = $weight * $squaredDiff;

                $sum += $weightedDiff;
                $totalWeight += $weight;

                $featureDetails[] = [
                    'feature' => $key,
                    'test_value' => $testVal,
                    'train_value' => $trainVal,
                    'difference' => $testVal - $trainVal,
                    'squared_diff' => $squaredDiff,
                    'feature_weight' => $weight,
                    'weighted_diff' => $weightedDiff
                ];

                Log::info("Perhitungan fitur", [
                    'feature' => $key,
                    'test_value' => $testVal,
                    'train_value' => $trainVal,
                    'difference' => $testVal - $trainVal,
                    'squared_diff' => $squaredDiff,
                    'feature_weight' => $weight,
                    'weighted_diff' => $weightedDiff
                ]);
            }
        }

        // Normalisasi jarak dengan total bobot
        $finalDistance = $totalWeight > 0 ? sqrt($sum / $totalWeight) : 0;

        Log::info("=== HASIL PERHITUNGAN JARAK BERBOBOT ===", [
            'sum' => $sum,
            'total_weight' => $totalWeight,
            'final_distance' => $finalDistance,
            'feature_details' => $featureDetails
        ]);

        return $finalDistance;
    }

    private function convertToNumeric($value)
    {
        $originalValue = $value;

        $map = [
            'baik' => 1,
            'cukup' => 0.5,
            'kurang' => 0
        ];

        if (is_numeric($value)) {
            $result = floatval($value);
            Log::info("Konversi nilai numerik", [
                'original_value' => $originalValue,
                'converted_value' => $result
            ]);
            return $result;
        }

        $value = strtolower(trim($value));
        $result = $map[$value] ?? 0;

        Log::info("Konversi nilai non-numerik", [
            'original_value' => $originalValue,
            'cleaned_value' => $value,
            'converted_value' => $result,
            'mapping_used' => $map[$value] ?? 'default (0)'
        ]);

        return $result;
    }

    public function uploadExcelDanPrediksi(Request $request)
    {
        Log::info("=== MULAI PROSES UPLOAD EXCEL DAN PREDIKSI ===");

        try {
            if (!$request->hasFile('excel_file')) {
                Log::error("File tidak ditemukan pada request");
                return response()->json(['status' => 'error', 'message' => 'File tidak ditemukan pada request!']);
            }

            $file = $request->file('excel_file');
            $k = 5; // Fixed K value

            Log::info("File Excel yang diupload", [
                'filename' => $file->getClientOriginalName(),
                'size' => $file->getSize(),
                'mime_type' => $file->getMimeType(),
                'k_value' => $k
            ]);

            // Validate file extension
            $extension = $file->getClientOriginalExtension();
            if (!in_array($extension, ['xlsx', 'xls', 'csv'])) {
                Log::error("Format file tidak didukung", ['extension' => $extension]);
                return back()->with('error', 'Format file tidak didukung. Gunakan file Excel (.xlsx, .xls) atau CSV.');
            }

            Log::info("Memulai proses membaca file Excel");
            $spreadsheet = IOFactory::load($file->getPathname());
            $worksheet = $spreadsheet->getActiveSheet();

            $rows = [];
            foreach ($worksheet->getRowIterator() as $index => $row) {
                $cellIterator = $row->getCellIterator();
                $cellIterator->setIterateOnlyExistingCells(false);
                $cells = [];

                foreach ($cellIterator as $cell) {
                    $cells[] = $cell->getFormattedValue();
                }

                if (array_filter($cells)) {
                    $rows[] = $cells;
                }
            }

            Log::info("Data yang dibaca dari Excel", [
                'total_rows' => count($rows),
                'first_row' => $rows[0] ?? null
            ]);

            if (count($rows) < 2) {
                Log::error("Data tidak cukup", ['row_count' => count($rows)]);
                return back()->with('error', 'Data tidak cukup (minimal header dan satu baris data).');
            }

            $headers = $rows[0];
            $dataRows = array_slice($rows, 1);
            $formattedRows = [];

            Log::info("Header yang ditemukan", ['headers' => $headers]);

            // Validate required headers
            $requiredHeaders = ['nama', 'nisn', 'semester_1', 'semester_2', 'semester_3', 'semester_4', 'semester_5', 'semester_6', 'usp', 'sikap', 'kerapian', 'kerajinan'];
            $missingHeaders = array_diff($requiredHeaders, array_map('strtolower', $headers));

            if (!empty($missingHeaders)) {
                Log::error("Header yang diperlukan tidak ditemukan", ['missing_headers' => $missingHeaders]);
                return back()->with('error', 'Format Excel tidak sesuai. Header yang diperlukan: ' . implode(', ', $missingHeaders));
            }

            Log::info("Memformat data dari Excel");
            foreach ($dataRows as $index => $dataRow) {
                $formatted = [];
                foreach ($headers as $i => $header) {
                    $formatted[strtolower(trim($header))] = trim($dataRow[$i] ?? '');
                }
                $formattedRows[] = $formatted;

                Log::info("Data baris " . ($index + 1), ['formatted_data' => $formatted]);
            }

            Log::info("Memulai transaksi database");
            DB::beginTransaction();
            $insertedStudents = [];
            $existingNISNs = [];

            foreach ($formattedRows as $index => $row) {
                $name = trim($row['nama'] ?? '');
                $nisn = trim($row['nisn'] ?? '');

                Log::info("Memproses baris " . ($index + 1), [
                    'name' => $name,
                    'nisn' => $nisn
                ]);

                if ($name === '' || $nisn === '') {
                    Log::warning("Baris " . ($index + 1) . " dilewati karena nama atau NISN kosong");
                    continue;
                }

                // Check for duplicate NISN
                if (in_array($nisn, $existingNISNs)) {
                    Log::error("NISN duplikat ditemukan", ['nisn' => $nisn]);
                    DB::rollback();
                    return back()->with('error', "NISN duplikat ditemukan: $nisn");
                }
                $existingNISNs[] = $nisn;

                // Check if NISN already exists as training data
                if (Student::where('nisn', $nisn)->where('jenis_data', 'training')->exists()) {
                    Log::error("NISN sudah terdaftar sebagai data latih", ['nisn' => $nisn]);
                    DB::rollback();
                    return back()->with('error', "NISN sudah terdaftar sebagai data latih: $nisn");
                }

                // Simpan siswa sebagai testing
                $student = Student::create([
                    'nisn' => $nisn,
                    'name' => $name,
                    'true_status' => null,
                    'jenis_data' => 'testing',
                ]);

                Log::info("Siswa berhasil dibuat", [
                    'student_id' => $student->id,
                    'nisn' => $student->nisn,
                    'name' => $student->name
                ]);

                // Simpan nilai-nilai atribut (kecuali nama dan NISN)
                $excludedFields = ['nama', 'nisn', 'status', 'jenis_data'];
                foreach ($row as $key => $value) {
                    if (!in_array(strtolower(trim($key)), $excludedFields) && trim($key) !== '' && $value !== null) {
                        StudentValue::create([
                            'student_id' => $student->id,
                            'key' => trim($key),
                            'value' => trim($value),
                        ]);

                        Log::info("Nilai siswa disimpan", [
                            'student_id' => $student->id,
                            'key' => trim($key),
                            'value' => trim($value)
                        ]);
                    }
                }

                $insertedStudents[] = $student;
            }

            if (empty($insertedStudents)) {
                Log::error("Tidak ada data valid yang dapat diproses");
                DB::rollback();
                return back()->with('error', 'Tidak ada data valid yang dapat diproses.');
            }

            Log::info("Commit transaksi database", ['inserted_count' => count($insertedStudents)]);
            DB::commit();

            // Jalankan prediksi untuk setiap siswa yang baru diinsert
            Log::info("Memulai prediksi untuk semua siswa yang diinsert");
            foreach ($insertedStudents as $index => $testStudent) {
                Log::info("Prediksi untuk siswa " . ($index + 1) . " dari " . count($insertedStudents), [
                    'student_id' => $testStudent->id,
                    'nisn' => $testStudent->nisn
                ]);
                $this->predictForStudent($testStudent, $k);
            }

            // Ambil hasil prediksi untuk semua siswa
            $excelPredictions = [];
            foreach ($insertedStudents as $student) {
                $prediction = Prediction::where('test_student_id', $student->id)->first();
                $neighbors = \App\Models\DistanceCalculation::with(['trainingStudent', 'weightCalculation'])
                    ->where('test_student_id', $student->id)
                    ->orderBy('distance')
                    ->limit($k)
                    ->get()
                    ->map(function ($item) {
                        return [
                            'nisn' => $item->trainingStudent->nisn ?? '-',
                            'nama' => $item->trainingStudent->name ?? '-',
                            'true_status' => $item->trainingStudent->true_status ?? '-',
                            'distance' => $item->distance,
                            'weight' => optional($item->weightCalculation)->weight,
                        ];
                    })->toArray();
                $ratios = \App\Models\WeightRatio::where('test_student_id', $student->id)->get(['class', 'total_weight', 'weight_ratio']);
                $excelPredictions[] = [
                    'nisn' => $student->nisn,
                    'name' => $student->name,
                    'status' => $prediction ? $prediction->predicted_status : '-',
                    'neighbors' => $neighbors,
                    'ratios' => $ratios
                ];
            }
            return view('pages.prediksi', [
                'excelPredictions' => $excelPredictions,
                'activeInputMethod' => 'excel'
            ])->with('success', 'Data Excel berhasil diproses dan diprediksi.');

        } catch (\Exception $e) {
            Log::error("Error dalam upload Excel dan prediksi", [
                'error_message' => $e->getMessage(),
                'error_trace' => $e->getTraceAsString()
            ]);
            DB::rollback();
            Log::error('Upload & Prediksi Error: ' . $e->getMessage());
            return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
        }
    }
}
