<?php

namespace App\Http\Controllers\HRM;

use App\Http\Controllers\Controller;
use App\Models\HRM\Attendance;
use App\Models\HRM\Attendancelog;
use Illuminate\Http\Request;
use App\Traits\CrudTrait;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;


class AttendanceController extends Controller
{
    use CrudTrait;
    protected string $dataType = 'object';
    protected string $modelClass = Attendance::class;
    //break time by default
    protected int $breakTime = 1; // 1 hour
    protected int $chunkSize = 1000; // 1000 records per chunk
    protected array $holydays = [
        // --- الأعياد الدينية ---
        ['name' => 'فاتح محرم', 'date' => '2025-07-07', 'days' => 1, 'type' => 'religious'], // 1 Muharram 1447
        ['name' => 'عيد المولد النبوي', 'date' => '2025-09-04', 'days' => 2, 'type' => 'religious'], // 12 Rabi' al-awwal
        ['name' => 'عيد الفطر', 'date' => '2025-03-31', 'days' => 2, 'type' => 'religious'], // 1 Shawwal
        ['name' => 'عيد الأضحى', 'date' => '2025-06-06', 'days' => 2, 'type' => 'religious'], // 10 Dhu al-Hijjah

        // --- الأعياد الوطنية ---
        ['name' => 'فاتح يناير', 'date' => '2025-01-01', 'days' => 1, 'type' => 'national'],
        ['name' => 'ذكرى تقديم عريضة الاستقلال', 'date' => '2025-01-11', 'days' => 1, 'type' => 'national'],
        ['name' => 'فاتح السنة الأمازيغية', 'date' => '2025-01-14', 'days' => 1, 'type' => 'national'],
        ['name' => 'عيد الشغل', 'date' => '2025-05-01', 'days' => 1, 'type' => 'national'],
        ['name' => 'عيد العرش', 'date' => '2025-07-30', 'days' => 1, 'type' => 'national'],
        ['name' => 'ذكرى وادي الذهب', 'date' => '2025-08-14', 'days' => 1, 'type' => 'national'],
        ['name' => 'ذكرى ثورة الملك والشعب', 'date' => '2025-08-20', 'days' => 1, 'type' => 'national'],
        ['name' => 'عيد الشباب', 'date' => '2025-08-21', 'days' => 1, 'type' => 'national'],
        ['name' => 'ذكرى المسيرة الخضراء', 'date' => '2025-11-06', 'days' => 1, 'type' => 'national'],
        ['name' => 'عيد الاستقلال', 'date' => '2025-11-18', 'days' => 1, 'type' => 'national'],


    ];

    public function index(Request $request)
    {
        return $this->getData($request);
    }

    public function store(Request $request)
    {
        return $this->storeData($request);
    }



    public function update(Request $request)
    {
        return $this->updateData($request);
    }

    public function bulkUpdate(Request $request)
    {
        return $this->multiUpdate($request);
    }


    public function destroy(Request $request)
    {
        return $this->destroyData($request);
    }

    public function bulkDestory(Request $request)
    {
        return $this->bulkDelete($request);
    }

    public function clone(Request $request)
    {
        return $this->cloneData($request);
    }

    public function export(Request $request)
    {
        return $this->exportData($request);
    }

    public function import(Request $request)
    {
        return $this->importData($request);
    }

    public function upload(Request $request)
    {
        return $this->uploadFile($request);
    }

    public function download(Request $request)
    {
        return $this->downloadFile($request);
    }

    public function generateAttendances()
    {
        $weekendDays = [0]; // Sunday


        $employees = AttendanceLog::select('employee_id', 'employee_pid', 'name', 'employee')
            ->distinct()
            ->get()
            ->map(fn($e) => [
                'employee_id' => (string) $e->employee_id,
                'employee_pid' => (string) $e->employee_pid,
                'name' => (string) $e->name,
                'employee' => json_encode($e->employee),
            ]);

        if ($employees->isEmpty()) {
            return response()->json([
                'status' => 'warning',
                'message' => 'Aucun employé trouvé dans les logs de présence.',
            ], 404);
        }

        $dates = AttendanceLog::where('isToken', false)
            ->select(DB::raw('DATE(date) as date'))
            ->distinct()
            ->pluck('date')
            ->toArray();

        $attendanceTemplate = [
            'uid' => null,
            'name' => null,
            'employee_id' => null,
            'employee_pid' => null,
            'status' => 'active',
            'employee' => null,
            'type' => 'work',
            'date' => null,
            'start' => null,
            'break' => null,
            'breakDuration' => 0,
            'end' => null,
            'lateWith' => null,
            'outEarlierWith' => null,
            'workedRegularHours' => 0,
            'workedSuppDayHours' => 0,
            'workedSuppNightHours' => 0,
            'workedSuppHolyDaysHours' => 0,
            'workedWeekendHours' => 0,
            'comment' => null,
            'created_at' => now(),
            'updated_at' => now(),
        ];

        $attendanceRecords = [];

        foreach ($employees as $employee) {
            foreach ($dates as $date) {
                $currentDate = Carbon::parse($date)->format('Y-m-d');
                $dayStart = Carbon::parse($date)->setHour(6)->setMinute(0)->setSecond(0);
                $dayEnd = $dayStart->copy()->addDay();

                $logs = AttendanceLog::where('employee_id', $employee['employee_id'])
                    ->where('isToken', false)
                    ->whereBetween('time', [$dayStart->toDateTimeString(), $dayEnd->toDateTimeString()])
                    ->orderBy('time')
                    ->get();

                $isHoliday = collect($this->holydays)->contains(function ($holiday) use ($currentDate) {
                    $start = Carbon::parse($holiday['date']);
                    $end = $start->copy()->addDays($holiday['days'] - 1);
                    $current = Carbon::parse($currentDate);
                    return $current->between($start, $end);
                });

                $holidayData = collect($this->holydays)->first(function ($holiday) use ($currentDate) {
                    $start = Carbon::parse($holiday['date']);
                    $end = $start->copy()->addDays($holiday['days'] - 1);
                    $current = Carbon::parse($currentDate);
                    return $current->between($start, $end);
                });


                $isWeekend = in_array(Carbon::parse($currentDate)->dayOfWeek, $weekendDays);

                $pairedLogs = $this->pairLogs($logs, $dayStart, $dayEnd);

                $workedRegularHours = $workedSuppDayHours = $workedSuppNightHours = $workedHolidayHours = $workedWeekendHours = 0;

                foreach ($pairedLogs as $pair) {
                    $workedRegularHours += $pair['calculated_hours']['regular'];
                    $workedSuppDayHours += $pair['calculated_hours']['supp_day'];
                    $workedSuppNightHours += $pair['calculated_hours']['supp_night'];
                    $workedHolidayHours += $pair['calculated_hours']['holiday'];
                    $workedWeekendHours += $pair['calculated_hours']['weekend'];
                }

                $firstIn = $pairedLogs[0]['in'] ?? null;
                $lastOut = end($pairedLogs)['out'] ?? null;

                $record = array_merge($attendanceTemplate, [
                    'uid' => (string) Str::uuid(),
                    'name' => $firstIn->name ?? $employee['name'],
                    'employee_id' => $employee['employee_id'],
                    'employee_pid' => $employee['employee_pid'],
                    'employee' => json_encode($employee),
                    'type' => $isHoliday ? 'bank_holiday' : ($isWeekend ? 'weekend' : 'work'),
                    'date' => $currentDate,
                    'break' =>  $this->breakTime ? Carbon::createFromTime($this->breakTime, 0, 0)->format('H:i:s') : null,
                    'breakDuration' =>  $this->breakTime ?? 0,
                    'start' => $firstIn  ? Carbon::parse($firstIn->time)->format('H:i:s') : null,
                    'end' => $lastOut  ? Carbon::parse($lastOut->time)->format('H:i:s') : null,
                    'workedRegularHours' => !$isHoliday && !$isWeekend  ? round($workedRegularHours - $this->breakTime, 2) : 0,
                    'workedSuppDayHours' => round($workedSuppDayHours, 2),
                    'workedSuppNightHours' => round($workedSuppNightHours, 2),
                    'workedSuppHolyDaysHours' => round($workedHolidayHours, 2),
                    'workedWeekendHours' => round($workedWeekendHours, 2),
                    'comment' => $isHoliday
                        ?  $holidayData['name'] . ' ' . $holidayData['days'] . ' jours'
                        : ($isWeekend ? 'Week-end — généré automatiquement' : 'Jour ouvrable — activité normale'),
                ]);

                $attendanceRecords[] = $record;
            }
        }

        return $this->bulkUpsertAttendances($attendanceRecords);
    }

    private function bulkUpsertAttendances(array $attendanceRecords)
    {

        if (empty($attendanceRecords)) {
            return response()->json(['status' => 'warning', 'message' => 'Aucune donnée à insérer.'], 404);
        }

        DB::beginTransaction();

        try {
            $chunkSize = 500;
            $uniqueBy = ['uid'];
            $updateFields = array_keys($attendanceRecords[0]);
            unset($updateFields[array_search('uid', $updateFields)]); // don't update uid
            collect($attendanceRecords)->chunk($chunkSize)->each(function ($chunk) use ($uniqueBy, $updateFields) {
                Attendance::upsert($chunk->toArray(), $uniqueBy, $updateFields);
            });

            $this->markAttendanceLogsAsProcessed($attendanceRecords);

            DB::commit();

            return response()->json([
                'status' => 'success',
                'message' => count($attendanceRecords) . ' enregistrements traités avec succès',
            ], 200);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
        }
    }

    // private function pairLogs($logs, Carbon $dayStart, Carbon $dayEnd): array
    // {
    //     $pairedLogs = [];
    //     $currentIn = null;
    //     foreach ($logs as $log) {
    //         if ($log->type === 'in') {
    //             // Close previous unpaired "in" if exists
    //             if ($currentIn) {
    //                 $pairedLogs[] = [
    //                     'in' => $currentIn,
    //                     'out' => (object)[
    //                         'id' => null,
    //                         'uid' => null,
    //                         'name' => $currentIn->name,
    //                         'employee_id' => $currentIn->employee_id,
    //                         'employee_pid' => $currentIn->employee_pid,
    //                         'status' => $currentIn->status,
    //                         'employee' => json_encode($currentIn->employee),
    //                         'date' => $currentIn->date,
    //                         'time' => $currentIn->time,
    //                         'type' => 'out',
    //                         'isToken' => false,
    //                         'comment' => 'No out, set same as in',
    //                         'deleted_at' => null,
    //                         'created_at' => null,
    //                         'updated_at' => null,
    //                     ]
    //                 ];
    //             }
    //             $currentIn = $log;
    //         } elseif ($log->type === 'out' && $currentIn) {
    //             $outTime = Carbon::parse($log->time);
    //             $inTime = Carbon::parse($currentIn->time);

    //             // If out is in current day range
    //             if ($outTime->gte($dayStart) && $outTime->lte($dayEnd)) {
    //                 $pairedLogs[] = [
    //                     'in' => $currentIn,
    //                     'out' => $log
    //                 ];
    //                 $currentIn = null;
    //             }
    //         }
    //     }

    //     // Handle unpaired "in" at the end of day
    //     if ($currentIn) {
    //         $pairedLogs[] = [
    //             'in' => $currentIn,
    //             'out' => (object)[
    //                 'id' => null,
    //                 'uid' => null,
    //                 'name' => $currentIn->name,
    //                 'employee_id' => $currentIn->employee_id,
    //                 'employee_pid' => $currentIn->employee_pid,
    //                 'status' => $currentIn->status,
    //                 'employee' => json_encode($currentIn->employee),
    //                 'date' => $currentIn->date,
    //                 'time' => $currentIn->time,
    //                 'type' => 'out',
    //                 'isToken' => false,
    //                 'comment' => 'No out found',
    //                 'deleted_at' => null,
    //                 'created_at' => null,
    //                 'updated_at' => null,
    //             ]
    //         ];
    //     }

    //     // Calculate hours for each pair
    //     foreach ($pairedLogs as &$pair) {
    //         $inTime = Carbon::parse($pair['in']->time);
    //         $outTime = Carbon::parse($pair['out']->time);

    //         $totalMinutes = $inTime->diffInMinutes($outTime);
    //         $currentTime = $inTime->copy();

    //         $pairRegular = 0;
    //         $pairSuppDay = 0;
    //         $pairSuppNight = 0;
    //         $pairHoliday = 0;
    //         $pairWeekend = 0;

    //         for ($m = 0; $m < $totalMinutes; $m++) {
    //             $hourMinute = $currentTime->format('H:i');
    //             $isHoliday = $this->isHoliday($currentTime);

    //             $isWeekend = in_array($currentTime->dayOfWeek, [0]); // Sunday

    //             $classified = false;

    //             if ($isHoliday) {
    //                 $pairHoliday += 1 / 60;
    //                 $classified = true;
    //             } elseif ($isWeekend) {
    //                 $pairWeekend += 1 / 60;
    //                 $classified = true;
    //             } else {
    //                 // Regular hours: 08:00-17:00
    //                 if ($hourMinute >= '06:00' && $hourMinute < '17:00') {
    //                     $pairRegular += 1 / 60;
    //                     $classified = true;
    //                 }
    //                 // Supplementary day hours: 06:00-08:00 & 17:00-21:00
    //                 //($hourMinute >= '06:00' && $hourMinute < '08:00') || this makes the 06:00-08:00 as supplementary day hours
    //                 if (!$classified &&  ($hourMinute >= '17:00' && $hourMinute < '21:00')) {
    //                     $pairSuppDay += 1 / 60;
    //                     $classified = true;
    //                 }
    //                 // Supplementary night: 21:00-06:00
    //                 if (!$classified && (($hourMinute >= '21:00' && $hourMinute <= '23:59') || ($hourMinute >= '00:00' && $hourMinute < '06:00'))) {
    //                     $pairSuppNight += 1 / 60;
    //                     $classified = true;
    //                 }
    //             }

    //             // If not classified yet, count as regular
    //             if (!$classified) $pairRegular += 1 / 60;

    //             $currentTime->addMinute();
    //         }

    //         $pair['calculated_hours'] = [
    //             'regular' => round($pairRegular, 2),
    //             'supp_day' => round($pairSuppDay, 2),
    //             'supp_night' => round($pairSuppNight, 2),
    //             'holiday' => round($pairHoliday, 2),
    //             'weekend' => round($pairWeekend, 2),
    //             'total' => round($pairRegular + $pairSuppDay + $pairSuppNight + $pairHoliday + $pairWeekend, 2),
    //         ];
    //     }
    //     return $pairedLogs;
    // }

    private function pairLogs($logs, Carbon $dayStart, Carbon $dayEnd): array
    {
        $pairs = [];
        $currentIn = null;

        // 1️⃣ Pair IN / OUT
        foreach ($logs as $log) {
            if ($log->type === 'in') {
                if ($currentIn) {
                    $pairs[] = ['in' => $currentIn, 'out' => $currentIn];
                }
                $currentIn = $log;
            } elseif ($log->type === 'out' && $currentIn) {
                $pairs[] = ['in' => $currentIn, 'out' => $log];
                $currentIn = null;
            }
        }

        if ($currentIn) {
            $pairs[] = ['in' => $currentIn, 'out' => $currentIn];
        }

        // 2️⃣ Calculate hours per pair (OPTIMIZED)
        foreach ($pairs as &$pair) {

            $in = Carbon::parse($pair['in']->time);
            $out = Carbon::parse($pair['out']->time);

            if ($out->lessThan($in)) {
                $out = $in;
            }

            $totalMinutes = $in->diffInMinutes($out);

            $regular = $suppDay = $suppNight = $holiday = $weekend = 0;

            //  Holiday
            if ($this->isHoliday($in)) {
                $holiday = $totalMinutes / 60;
            }
            //  Weekend (Sunday)
            elseif ($in->dayOfWeek === Carbon::SUNDAY) {
                $weekend = $totalMinutes / 60;
            }
            //  Normal working day
            else {
                $regular += $this->minutesBetween($in, $out, '06:00', '18:00') / 60;
                $suppDay += $this->minutesBetween($in, $out, '18:00', '21:00') / 60;
                $suppNight += (
                    $this->minutesBetween($in, $out, '21:00', '23:59') +
                    $this->minutesBetween($in, $out, '00:00', '06:00')
                ) / 60;
            }

            $pair['calculated_hours'] = [
                'regular' => round($regular, 2),
                'supp_day' => round($suppDay, 2),
                'supp_night' => round($suppNight, 2),
                'holiday' => round($holiday, 2),
                'weekend' => round($weekend, 2),
                'total' => round($regular + $suppDay + $suppNight + $holiday + $weekend, 2),
            ];
        }

        return $pairs;
    }

    private function minutesBetween(Carbon $start, Carbon $end, string $from, string $to): int
    {
        $rangeStart = Carbon::parse($start->format('Y-m-d') . ' ' . $from);
        $rangeEnd   = Carbon::parse($start->format('Y-m-d') . ' ' . $to);

        if ($rangeEnd->lessThan($rangeStart)) {
            $rangeEnd->addDay();
        }

        $realStart = $start->greaterThan($rangeStart) ? $start : $rangeStart;
        $realEnd   = $end->lessThan($rangeEnd) ? $end : $rangeEnd;

        return max(0, $realStart->diffInMinutes($realEnd));
    }



    private function isHoliday($date)
    {
        foreach ($this->holydays as $holiday) {
            $start = Carbon::parse($holiday['date'])->startOfDay();
            $end   = Carbon::parse($holiday['date'])
                ->addDays($holiday['days'] - 1)
                ->endOfDay();

            if ($date->between($start, $end)) {
                return true;
            }
        }

        return false;
    }


    private function markAttendanceLogsAsProcessed(array $attendanceRecords): int
    {
        try {
            // Extract all unique dates and employee IDs from the attendance records
            $dates = array_unique(array_column($attendanceRecords, 'date'));
            $employeeIds = array_unique(array_column($attendanceRecords, 'employee_id'));
            // Update AttendanceLogs that match these dates and employee IDs
            $updatedCount = AttendanceLog::whereIn('date', $dates)
                ->whereIn('employee_id', $employeeIds)
                ->where('isToken', false) // Only update unprocessed logs
                ->update(['isToken' => true]);

            return $updatedCount;
        } catch (\Exception $e) {
            throw $e;
        }
    }
}
