<?php

namespace App\Modules\HR\Attendance\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Modules\HR\Attendance\Models\AttendanceDetail;
use App\Modules\HR\Attendance\Models\AttendancePunchDetail;
use App\Modules\HR\Attendance\Services\AttendanceMarkingService;
use App\Modules\HR\Core\Models\Employee;
use App\Modules\HR\Core\Models\Shift;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;

class AttendanceController extends Controller
{
    protected AttendanceMarkingService $markingService;

    public function __construct(AttendanceMarkingService $markingService)
    {
        $this->markingService = $markingService;
    }

    public function updateRow(Request $request)
    {
        DB::transaction(function () use ($request) {

            $detail = AttendanceDetail::findOrFail($request->id);

            // Use times from request as-is (frontend already handles setting/clearing based on status)
            $inTime = $request->in_time;
            $outTime = $request->out_time;

            // Determine leave code based on leave application and in/out times
            $leaveDate = \App\Modules\HR\Leave\Models\LeaveApplicationDate::where('employee_id', $detail->employee_id)
                ->where('leave_date', $detail->attendance->attendance_date)
                ->first();

            $leaveCode = null;
            if ($leaveDate) {
                // Has leave application
                if ($inTime) {
                    // Has in/out times (worked) - mark as Present with Leave
                    $leaveCode = 'P(L)';
                } else {
                    // No in/out times (didn't work) - just on leave
                    $leaveCode = 'L';
                }
            } else {
                // No leave application - use attendance status
                $leaveCode = $request->status;
            }

            $detail->update([
                'shift_id' => $request->shift_id,
                'attendance_status' => $request->status,
                'leave_code' => $leaveCode,
                'in_time' => $inTime,
                'out_time' => $outTime,
                'lunch_taken' => $request->lunch,
                'dinner_taken' => $request->dinner,
                'remove_ot' => $request->remove_ot,
                'is_manual' => true,
            ]);

            // Punch update
            foreach ($request->punches as $p) {
                AttendancePunchDetail::updateOrCreate(
                    [
                        'attendance_detail_id' => $detail->id,
                        'sequence_no' => $p['seq'],
                    ],
                    [
                        'punch_time' => $p['datetime'],
                        'source' => 'MANUAL',
                    ]
                );
            }

            // For manual edits, only recalculate minutes (late, early, OT) without changing attendance_status
            // Get the shift to calculate minutes
            if ($detail->shift_id && ($inTime || $outTime)) {
                $shift = Shift::withoutGlobalScope('branch_context')->find($detail->shift_id);
                if ($shift && $inTime) {
                    // Parse string times to Carbon objects
                    $inTimeCarbon = \Carbon\Carbon::parse($inTime);
                    $outTimeCarbon = $outTime ? \Carbon\Carbon::parse($outTime) : null;

                    $this->calculateMinutesOnly($detail, $shift, $inTimeCarbon, $outTimeCarbon);
                    $detail->save();
                } else {
                    // No valid shift or times, zero out minutes
                    $detail->update([
                        'late_minutes' => 0,
                        'early_minutes' => 0,
                        'ot_minutes' => 0,
                        'dot_minutes' => 0,
                    ]);
                }
            } else {
                // No shift or times, zero out minutes
                $detail->update([
                    'late_minutes' => 0,
                    'early_minutes' => 0,
                    'ot_minutes' => 0,
                    'dot_minutes' => 0,
                ]);
            }
        });

        return response()->json(['status' => true]);
    }

    public function bulkUpdate(Request $request)
    {
        $request->validate([
            'rows' => 'nullable|array',
            'rows.*.id' => 'exists:attendance_details,id',
            'ids' => 'nullable|array',
            'ids.*' => 'exists:attendance_details,id',
            'shift_id' => 'nullable|exists:shifts,id',
            'status' => 'nullable|string',
        ]);

        DB::transaction(function () use ($request) {
            // New format: full rows data
            if ($request->has('rows')) {
                foreach ($request->rows as $rowData) {
                    $detail = AttendanceDetail::findOrFail($rowData['id']);

                    // Determine leave code based on leave application and in/out times
                    $leaveDate = \App\Modules\HR\Leave\Models\LeaveApplicationDate::where('employee_id', $detail->employee_id)
                        ->where('leave_date', $detail->attendance->attendance_date)
                        ->first();

                    $leaveCode = null;
                    if ($leaveDate) {
                        // Has leave application
                        if ($rowData['in_time']) {
                            // Has in/out times (worked) - mark as Present with Leave
                            $leaveCode = 'P(L)';
                        } else {
                            // No in/out times (didn't work) - just on leave
                            $leaveCode = 'L';
                        }
                    } else {
                        // No leave application - use attendance status
                        $leaveCode = $rowData['status'];
                    }

                    $detail->update([
                        'shift_id' => $rowData['shift_id'],
                        'attendance_status' => $rowData['status'],
                        'leave_code' => $leaveCode,
                        'in_time' => $rowData['in_time'],
                        'out_time' => $rowData['out_time'],
                        'lunch_taken' => $rowData['lunch'],
                        'dinner_taken' => $rowData['dinner'],
                        'remove_ot' => $rowData['remove_ot'],
                        'is_manual' => true,
                    ]);

                    // Punch update
                    if (isset($rowData['punches'])) {
                        foreach ($rowData['punches'] as $p) {
                            AttendancePunchDetail::updateOrCreate(
                                [
                                    'attendance_detail_id' => $detail->id,
                                    'sequence_no' => $p['seq'],
                                ],
                                [
                                    'punch_time' => $p['datetime'],
                                    'source' => 'MANUAL',
                                ]
                            );
                        }
                    }

                    // For manual edits, only recalculate minutes without changing attendance_status
                    if ($detail->shift_id && ($rowData['in_time'] || $rowData['out_time'])) {
                        $shift = Shift::withoutGlobalScope('branch_context')->find($detail->shift_id);
                        if ($shift && $rowData['in_time']) {
                            $inTime = \Carbon\Carbon::parse($rowData['in_time']);
                            $outTime = $rowData['out_time'] ? \Carbon\Carbon::parse($rowData['out_time']) : null;
                            $this->calculateMinutesOnly($detail, $shift, $inTime, $outTime);
                            $detail->save();
                        } else {
                            $detail->update([
                                'late_minutes' => 0,
                                'early_minutes' => 0,
                                'ot_minutes' => 0,
                                'dot_minutes' => 0,
                            ]);
                        }
                    } else {
                        $detail->update([
                            'late_minutes' => 0,
                            'early_minutes' => 0,
                            'ot_minutes' => 0,
                            'dot_minutes' => 0,
                        ]);
                    }
                }
                return;
            }

            // Legacy format: ids + shift_id/status
            $updateData = [];
            if ($request->filled('shift_id')) $updateData['shift_id'] = $request->shift_id;
            if ($request->filled('status')) $updateData['attendance_status'] = $request->status;
            
            if ($request->filled('shift_id')) {
                $shift = Shift::find($request->shift_id);
                if ($shift) {
                    $details = AttendanceDetail::with('attendance')
                        ->whereIn('id', $request->ids)
                        ->get();
                    
                    foreach ($details as $detail) {
                        $date = $detail->attendance->attendance_date->format('Y-m-d');
                        $detail->update([
                            'shift_id' => $shift->id,
                            'attendance_status' => $request->status ?? $detail->attendance_status,
                            'in_time' => $date . ' ' . $shift->work_start_time,
                            'out_time' => $date . ' ' . $shift->work_end_time,
                            'is_manual' => true,
                        ]);
                    }
                }
            }
            
            if (!empty($updateData)) {
                $updateData['is_manual'] = true;
                AttendanceDetail::whereIn('id', $request->ids)->update($updateData);

                // Recalculate for legacy format
                $details = AttendanceDetail::with(['attendance', 'employee'])->whereIn('id', $request->ids)->get();
                foreach ($details as $detail) {
                    $this->markingService->markEmployeeDateAttendance(
                        $detail->employee,
                        $detail->attendance->attendance_date->format('Y-m-d'),
                        $detail->attendance->branch_id,
                        false,
                        $detail->shift_id
                    );
                }
            }
        });

        return response()->json(['status' => true, 'message' => 'Bulk update successful']);
    }

    public function getShiftTimes(Request $request)
    {
        $request->validate(['shift_id' => 'required|exists:shifts,id']);

        $shift = Shift::findOrFail($request->shift_id);

        return response()->json([
            'work_start_time' => substr($shift->work_start_time, 0, 5), // HH:MM
            'work_end_time' => substr($shift->work_end_time, 0, 5),     // HH:MM
        ]);
    }

    /**
     * Calculate only late, early, and OT minutes without changing attendance status
     */
    private function calculateMinutesOnly(AttendanceDetail $detail, Shift $shift, $inTime, $outTime): void
    {
        // Safety check: If inTime is exactly midnight (00:00:00), skip unless shift starts at midnight
        $hasValidInTime = $inTime && $inTime->format('H:i:s') !== '00:00:00';
        $shiftStartsAtMidnight = $shift->work_start_time === '00:00:00' || $shift->work_start_time === '00:00';

        if (!$hasValidInTime && !$shiftStartsAtMidnight) {
            $detail->late_minutes = 0;
            $detail->early_minutes = 0;
            $detail->ot_minutes = 0;
            $detail->dot_minutes = 0;
            return;
        }

        $date = $inTime->toDateString();
        $shiftStart = \Carbon\Carbon::parse($date . ' ' . $shift->work_start_time);
        $shiftEnd = \Carbon\Carbon::parse($date . ' ' . $shift->work_end_time);

        if ($shiftEnd->lt($shiftStart)) { $shiftEnd->addDay(); }

        // 1. Late/Early Calculation
        $lateGrace = $shift->late_grace_minutes ?? 0;
        $actualShiftStart = $shiftStart->copy()->addMinutes($lateGrace);

        if ($inTime->gt($actualShiftStart)) {
            $detail->late_minutes = (int) round(abs($inTime->diffInMinutes($shiftStart)));
        } else {
            $detail->late_minutes = 0;
        }

        if ($outTime) {
            $earlyGrace = $shift->early_grace_minutes ?? 0;
            $actualShiftEnd = $shiftEnd->copy()->subMinutes($earlyGrace);

            if ($outTime->lt($actualShiftEnd)) {
                $detail->early_minutes = (int) round(abs($outTime->diffInMinutes($shiftEnd)));
            } else {
                $detail->early_minutes = 0;
            }

            // 2. OT/DOT Calculation based on shift_details
            $otMinutes = 0;
            $dotMinutes = 0;

            $details = $shift->details; // Already ordered by sort_order

            foreach ($details as $slot) {
                $slotStart = \Carbon\Carbon::parse($date . ' ' . $slot->start_time);
                $slotEnd = \Carbon\Carbon::parse($date . ' ' . $slot->end_time);

                // Handle slots crossing midnight
                if ($slotEnd->lt($slotStart)) { $slotEnd->addDay(); }

                // Handle slots that should be on the next day (for night shifts)
                if ($slotEnd->lte($shiftStart) && $shiftEnd->gt($shiftStart)) {
                    $slotStart->addDay();
                    $slotEnd->addDay();
                }

                // Intersection of [inTime, outTime] and [slotStart, slotEnd]
                $intersectStart = $inTime->gt($slotStart) ? $inTime : $slotStart;
                $intersectEnd = $outTime->lt($slotEnd) ? $outTime : $slotEnd;

                if ($intersectStart->lt($intersectEnd)) {
                    $minutes = $intersectStart->diffInMinutes($intersectEnd);

                    if ($slot->slot_type === 'OT' || $slot->slot_type === 'TOT') {
                        $otMinutes += $minutes;
                    } elseif ($slot->slot_type === 'DOT') {
                        $dotMinutes += $minutes;
                    } elseif ($slot->slot_type === 'LUNCH' && !$detail->lunch_taken) {
                        $otMinutes += $minutes;
                    } elseif ($slot->slot_type === 'DINNER' && !$detail->dinner_taken) {
                        $otMinutes += $minutes;
                    }
                }
            }

            // Respect remove_ot flag
            if ($detail->remove_ot) {
                $otMinutes = 0;
                $dotMinutes = 0;
            }

            $detail->ot_minutes = $otMinutes;
            $detail->dot_minutes = $dotMinutes;

        } else {
            $detail->early_minutes = 0;
            $detail->ot_minutes = 0;
            $detail->dot_minutes = 0;
        }
    }

}
