<?php
/**
 * EpposGLogParser.php  — versi 2
 * app/Services/EpposGLogParser.php
 */

namespace App\Services;

use PhpOffice\PhpSpreadsheet\IOFactory;
use Carbon\Carbon;

class EpposGLogParser
{
    const IOMD_MASUK  = 2;
    const IOMD_PULANG = 4;

    const SLOT_KEYS = [
        'jk1_msuk',
        'jk1_kluar',
        'jk2_msuk',
        'jk2_kluar',
        'lb_msuk',
        'lb_kluar',
    ];

    const SLOT_IOMD = [
        'jk1_msuk'  => self::IOMD_MASUK,
        'jk1_kluar' => self::IOMD_PULANG,
        'jk2_msuk'  => self::IOMD_MASUK,
        'jk2_kluar' => self::IOMD_PULANG,
        'lb_msuk'   => self::IOMD_MASUK,
        'lb_kluar'  => self::IOMD_PULANG,
    ];

    // ─────────────────────────────────────────────────────────────
    // PARSE INFO.XLS
    // ─────────────────────────────────────────────────────────────

    public function parseInfoFile(string $path): array
    {
        $spreadsheet = IOFactory::load($path);

        return [
            'shifts' => $this->parseShifts($spreadsheet->getSheetByName('Shift')),
            'jadwal' => $this->parseJadwal($spreadsheet->getSheetByName('Jadwal')),
        ];
    }

    private function parseShifts($sheet): array
    {
        $shifts = [];
        for ($row = 6; $row <= $sheet->getHighestRow(); $row++) {
            $no = $sheet->getCell("A{$row}")->getValue();
            if (!is_numeric($no)) continue;

            $s = [
                'jk1_msuk'  => $this->readTime($sheet->getCell("B{$row}")->getValue()),
                'jk1_kluar' => $this->readTime($sheet->getCell("C{$row}")->getValue()),
                'jk2_msuk'  => $this->readTime($sheet->getCell("D{$row}")->getValue()),
                'jk2_kluar' => $this->readTime($sheet->getCell("E{$row}")->getValue()),
                'lb_msuk'   => $this->readTime($sheet->getCell("F{$row}")->getValue()),
                'lb_kluar'  => $this->readTime($sheet->getCell("G{$row}")->getValue()),
            ];

            $adaIsi = array_filter($s);
            if (empty($adaIsi)) continue;

            $shifts[(int)$no] = $s;
        }
        return $shifts;
    }

    private function parseJadwal($sheet): array
    {
        $jadwal = [];
        for ($row = 3; $row <= $sheet->getHighestRow(); $row++) {
            $no   = $sheet->getCell("A{$row}")->getValue();
            $nama = $sheet->getCell("B{$row}")->getValue();
            if (!is_numeric($no) || empty($nama)) continue;

            $jadwal[(string)(int)$no] = [
                'nama'  => trim((string)$nama),
                'dept'  => trim((string)($sheet->getCell("C{$row}")->getValue() ?? '')),
                'shift' => (int)($sheet->getCell("D{$row}")->getValue() ?? 1),
            ];
        }
        return $jadwal;
    }

    // ─────────────────────────────────────────────────────────────
    // PARSE GLOG.TXT
    // ─────────────────────────────────────────────────────────────

    public function parseGLog(string $path): array
    {
        $content = file_get_contents($path);
        $content = str_replace(["\r\n", "\r"], "\n", $content);
        $lines   = explode("\n", trim($content));
        $records = [];

        foreach ($lines as $line) {
            $line = trim($line);
            if (empty($line)) continue;

            $cols = explode("\t", $line);
            $cols = array_values(array_filter(array_map('trim', $cols), fn($v) => $v !== ''));

            if (count($cols) < 7) continue;
            if ($cols[0] === 'No') continue;

            $enno      = $cols[2] ?? '';
            $namaMesin = $cols[3] ?? '';
            $iomdRaw   = $cols[5] ?? '';
            $dtRaw     = $cols[6] ?? '';

            if (!is_numeric(ltrim($enno, '0') ?: '0')) continue;
            if (empty($dtRaw)) continue;

            $iomd = (int)$iomdRaw;
            if (!in_array($iomd, [self::IOMD_MASUK, self::IOMD_PULANG])) continue;

            $dtRaw  = preg_replace('/\s+/', ' ', trim($dtRaw));
            $parts  = explode(' ', $dtRaw);
            if (count($parts) < 2) continue;

            $tglStr = $parts[0];
            $jamStr = substr($parts[1], 0, 5);

            if (!preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $tglStr)) continue;
            if (!preg_match('/^\d{2}:\d{2}$/', $jamStr)) continue;

            $tanggal = str_replace('/', '-', $tglStr);
            $idMesin = (string)(int)ltrim($enno, '0') ?: '0';

            $records[] = [
                'id_mesin'   => $idMesin,
                'nama_mesin' => trim($namaMesin),
                'tanggal'    => $tanggal,
                'jam'        => $jamStr,
                'iomd'       => $iomd,
                'dt_raw'     => $dtRaw,
            ];
        }

        return $records;
    }

    // ─────────────────────────────────────────────────────────────
    // GROUP BY DAY
    // ─────────────────────────────────────────────────────────────

    public function groupGLogByDay(array $records): array
    {
        $grouped = [];

        foreach ($records as $r) {
            $key = "{$r['id_mesin']}_{$r['tanggal']}";

            if (!isset($grouped[$key])) {
                $grouped[$key] = [
                    'id_mesin'   => $r['id_mesin'],
                    'nama_mesin' => $r['nama_mesin'],
                    'tanggal'    => $r['tanggal'],
                    'scans'      => [],
                ];
            }

            $duplikat = array_filter(
                $grouped[$key]['scans'],
                fn($s) => $s['jam'] === $r['jam'] && $s['iomd'] === $r['iomd']
            );
            if (!empty($duplikat)) continue;

            $grouped[$key]['scans'][] = [
                'jam'  => $r['jam'],
                'iomd' => $r['iomd'],
            ];
        }

        foreach ($grouped as &$g) {
            usort($g['scans'], fn($a, $b) => strcmp($a['jam'], $b['jam']));
        }

        return $grouped;
    }

    // ─────────────────────────────────────────────────────────────
    // MATCH TO KEGIATAN
    // ─────────────────────────────────────────────────────────────

    /**
     * Cocokkan setiap scan ke kegiatan web.
     *
     * STATUS Hadir / Terlambat ditentukan berdasarkan $tolSesudah:
     * ─────────────────────────────────────────────────────────────
     *   scan <= waktu_mulai + $tolSesudah  → Hadir
     *   scan >  waktu_mulai + $tolSesudah  → Terlambat
     *
     * Contoh: tolSesudah=30, kegiatan mulai 04:00
     *   Scan 04:07 (+7 menit)  → Hadir    ✅  (7 ≤ 30)
     *   Scan 04:35 (+35 menit) → Terlambat ⏰  (35 > 30)
     * ─────────────────────────────────────────────────────────────
     *
     * @param array $glogGrouped  Output groupGLogByDay()
     * @param array $infoData     Output parseInfoFile()
     * @param array $kegiatans    Dari DB
     * @param int   $tolSebelum   Menit toleransi SEBELUM waktu_mulai (window masuk)
     * @param int   $tolSesudah   Menit toleransi SESUDAH waktu_mulai (Hadir/Terlambat cut-off)
     */
    public function matchToKegiatan(
        array $glogGrouped,
        array $infoData,
        array $kegiatans,
        int $tolSebelum = 15,
        int $tolSesudah = 10
    ): array {
        $hasil = [];

        foreach ($glogGrouped as $dayData) {
            $tanggal    = $dayData['tanggal'];
            $idMesin    = $dayData['id_mesin'];
            $scans      = $dayData['scans'];
            $hari       = $this->tanggalToHari($tanggal);

            $kegHariIni = array_values(
                array_filter($kegiatans, fn($k) => $k['hari'] === $hari)
            );

            $jadwalInfo = $infoData['jadwal'][$idMesin] ?? null;
            $nomorShift = $jadwalInfo ? ($jadwalInfo['shift'] ?? 1) : null;
            $shiftData  = ($nomorShift && isset($infoData['shifts'][$nomorShift]))
                          ? $infoData['shifts'][$nomorShift]
                          : null;

            $slotWindows = $shiftData
                ? $this->buildSlotWindows($shiftData)
                : [];

            $matchedKg = [];
            $usedScans = [];
            $rowMap    = [];

            foreach ($scans as $idx => $scan) {
                $scanJam  = $scan['jam'];
                $scanIomd = $scan['iomd'];
                $scanMnt  = $this->toMinutes($scanJam);

                $bestKg      = null;
                $bestSelisih = PHP_INT_MAX;
                $bestSlot    = null;

                if (!empty($slotWindows)) {
                    // ── MODE UTAMA: cocokkan pakai IOMd dari shift ──────
                    foreach ($slotWindows as $sw) {
                        if ($sw['iomd'] !== $scanIomd) continue;
                        if ($sw['jam'] === null) continue;

                        $slotMnt     = $this->toMinutes($sw['jam']);
                        $windowMulai = $slotMnt - $tolSebelum;
                        $windowAkhir = $slotMnt + $tolSesudah;

                        if ($scanMnt < $windowMulai || $scanMnt > $windowAkhir) continue;

                        foreach ($kegHariIni as $kg) {
                            if (isset($matchedKg[$kg['kegiatan_id']])) continue;

                            $kgMulaiMnt   = $this->toMinutes($kg['waktu_mulai']);
                            $kgSelesaiMnt = $this->toMinutes($kg['waktu_selesai'] ?: $kg['waktu_mulai']);
                            $kgWindowMul  = $kgMulaiMnt - $tolSebelum;
                            $kgWindowAkh  = $kgSelesaiMnt + $tolSesudah;

                            if ($slotMnt < $kgWindowMul || $slotMnt > $kgWindowAkh) continue;

                            $selisih = abs($slotMnt - $kgMulaiMnt);
                            if ($selisih < $bestSelisih) {
                                $bestSelisih = $selisih;
                                $bestKg      = $kg;
                                $bestSlot    = $sw;
                            }
                        }
                    }
                } else {
                    // ── FALLBACK: shifts kosong, matching hanya berdasar jam ──
                    foreach ($kegHariIni as $kg) {
                        if (isset($matchedKg[$kg['kegiatan_id']])) continue;

                        $kgMulaiMnt   = $this->toMinutes($kg['waktu_mulai']);
                        $kgSelesaiMnt = $this->toMinutes($kg['waktu_selesai'] ?: $kg['waktu_mulai']);
                        $kgWindowMul  = $kgMulaiMnt - $tolSebelum;
                        $kgWindowAkh  = $kgSelesaiMnt + $tolSesudah;

                        if ($scanMnt < $kgWindowMul || $scanMnt > $kgWindowAkh) continue;

                        $selisih = abs($scanMnt - $kgMulaiMnt);
                        if ($selisih < $bestSelisih) {
                            $bestSelisih = $selisih;
                            $bestKg      = $kg;
                        }
                    }
                }

                if ($bestKg) {
                    $kgMulaiMnt = $this->toMinutes($bestKg['waktu_mulai']);
                    $selisih    = $scanMnt - $kgMulaiMnt;

                    // ─────────────────────────────────────────────────────
                    // STATUS Hadir vs Terlambat:
                    //   Hadir     → scan tiba SEBELUM atau tepat waktu +$tolSesudah menit
                    //   Terlambat → scan tiba LEBIH dari $tolSesudah menit setelah mulai
                    //
                    // Dengan begitu: toleransi dari form LANGSUNG menentukan batas
                    // Hadir/Terlambat — tidak ada nilai hardcode di sini.
                    // ─────────────────────────────────────────────────────
                    $status         = $selisih <= $tolSesudah ? 'Hadir' : 'Terlambat';
                    $selisihTampil  = $selisih > 0 ? $selisih : 0; // menit telat (hanya jika positif)

                    $matchedKg[$bestKg['kegiatan_id']] = true;
                    $usedScans[] = $idx;

                    $rowMap[$bestKg['kegiatan_id']] = [
                        'kegiatan_id'   => $bestKg['kegiatan_id'],
                        'nama_kegiatan' => $bestKg['nama'],
                        'waktu_mulai'   => $bestKg['waktu_mulai'],
                        'jam_scan'      => $scanJam,
                        'iomd_scan'     => $scanIomd,
                        'label_iomd'    => $scanIomd === self::IOMD_MASUK ? 'Masuk' : 'Pulang',
                        'status'        => $status,
                        'selisih_menit' => $selisihTampil,
                        'matched'       => true,
                    ];
                }
            }

            // ── Alpa untuk kegiatan tanpa scan ───────────────────
            foreach ($kegHariIni as $kg) {
                if (!isset($rowMap[$kg['kegiatan_id']])) {
                    $rowMap[$kg['kegiatan_id']] = [
                        'kegiatan_id'   => $kg['kegiatan_id'],
                        'nama_kegiatan' => $kg['nama'],
                        'waktu_mulai'   => $kg['waktu_mulai'],
                        'jam_scan'      => null,
                        'iomd_scan'     => null,
                        'label_iomd'    => null,
                        'status'        => 'Alpa',
                        'selisih_menit' => null,
                        'matched'       => false,
                    ];
                }
            }

            $unmatchedScans = [];
            foreach ($scans as $idx => $scan) {
                if (!in_array($idx, $usedScans)) {
                    $unmatchedScans[] = $scan['jam']
                        . ' (' . ($scan['iomd'] === 2 ? 'Masuk' : 'Pulang') . ')';
                }
            }

            $rows = collect($rowMap)->sortBy('waktu_mulai')->values()->toArray();

            $hasil[] = [
                'id_mesin'        => $idMesin,
                'nama_mesin'      => $dayData['nama_mesin'],
                'tanggal'         => $tanggal,
                'hari'            => $hari,
                'all_scans'       => $scans,
                'unmatched_scans' => $unmatchedScans,
                'shift_dipakai'   => $nomorShift,
                'rows'            => $rows,
            ];
        }

        return $hasil;
    }

    // ─────────────────────────────────────────────────────────────
    // BUILD SLOT WINDOWS
    // ─────────────────────────────────────────────────────────────

    private function buildSlotWindows(array $shiftData): array
    {
        $windows = [];
        foreach (self::SLOT_KEYS as $slotKey) {
            $windows[] = [
                'slot' => $slotKey,
                'jam'  => $shiftData[$slotKey] ?? null,
                'iomd' => self::SLOT_IOMD[$slotKey],
            ];
        }
        return $windows;
    }

    // ─────────────────────────────────────────────────────────────
    // HELPERS
    // ─────────────────────────────────────────────────────────────

    private function toMinutes(string $hhmm): int
    {
        if (!str_contains($hhmm, ':')) return 0;
        [$h, $m] = explode(':', $hhmm);
        return (int)$h * 60 + (int)$m;
    }

    public function tanggalToHari(string $tanggal): string
    {
        return [
            'Monday'    => 'Senin',
            'Tuesday'   => 'Selasa',
            'Wednesday' => 'Rabu',
            'Thursday'  => 'Kamis',
            'Friday'    => 'Jumat',
            'Saturday'  => 'Sabtu',
            'Sunday'    => 'Ahad',
        ][Carbon::parse($tanggal)->format('l')] ?? 'Senin';
    }

    private function readTime($val): ?string
    {
        if ($val === null || $val === '') return null;

        if (is_float($val) || (is_string($val) && is_numeric($val) && str_contains($val, '.'))) {
            $totalMin = round((float)$val * 24 * 60);
            return sprintf('%02d:%02d', intdiv($totalMin, 60), $totalMin % 60);
        }

        $str = preg_replace('/\s+/', '', trim((string)$val));
        if (preg_match('/^(\d{1,2}):(\d{2})$/', $str, $m)) {
            return sprintf('%02d:%02d', (int)$m[1], (int)$m[2]);
        }
        return null;
    }
}