<?php

namespace App\Services;

use App\Models\Attendance;
use App\Models\User;
use Carbon\Carbon;

class AttendanceService
{
    public function __construct(protected NotificationService $notificationService) {}

    /**
     * Clock in the user.
     */
    /**
     * Clock in the user.
     */
    public function clockIn(User $user, ?string $note = null, bool $manual = false, string $workMode = 'office'): Attendance
    {
        $date = Carbon::today()->toDateString();

        $attendance = Attendance::updateOrCreate(
            ['user_id' => $user->id, 'date' => $date],
            [
                'clock_in' => Carbon::now('UTC'),
                'in_type' => $manual ? 'manual' : 'auto',
                'in_note' => $note,
                'work_mode' => $workMode,
                'status' => $manual ? 'pending' : 'approved',
            ]
        );

        // Detect and flag anomalies (late check-in)
        $this->detectAndFlagAnomalies($attendance);

        return $attendance;
    }

    /**
     * Start a break for the user.
     */
    public function startBreak(User $user): ?Attendance
    {
        $attendance = $this->getTodayStatus($user);

        if (! $attendance || ! $attendance->clock_in || $attendance->clock_out || $attendance->break_start) {
            return null;
        }

        $attendance->update([
            'break_start' => Carbon::now('UTC'),
        ]);

        return $attendance;
    }

    /**
     * End a break for the user.
     */
    public function endBreak(User $user): ?Attendance
    {
        $attendance = $this->getTodayStatus($user);

        if (! $attendance || ! $attendance->break_start) {
            return null;
        }

        $breakDuration = $attendance->break_start->diffInSeconds(Carbon::now('UTC'));

        $attendance->update([
            'break_start' => null,
            'total_break_seconds' => $attendance->total_break_seconds + $breakDuration,
        ]);

        return $attendance;
    }

    /**
     * Clock out the user.
     */
    public function clockOut(User $user, ?string $note = null, bool $manual = false): ?Attendance
    {
        $date = Carbon::today()->toDateString();
        $attendance = Attendance::where('user_id', $user->id)->where('date', $date)->first();

        if (! $attendance) {
            return null;
        }

        // If currently on break, end it first
        if ($attendance->break_start) {
            $this->endBreak($user);
            $attendance->refresh();
        }

        $clockOut = Carbon::now('UTC');
        $totalHours = $attendance->clock_in ? $attendance->clock_in->diffInMinutes($clockOut) / 60 : 0;

        // Subtract break time from total hours
        $totalHours -= ($attendance->total_break_seconds / 3600);

        $attendance->update([
            'clock_out' => $clockOut,
            'out_type' => $manual ? 'manual' : 'auto',
            'out_note' => $note,
            'total_hours' => round(max(0, $totalHours), 2),
            'status' => ($manual || $attendance->status === 'pending') ? 'pending' : 'approved',
        ]);

        // Detect and flag anomalies (early check-out)
        $this->detectAndFlagAnomalies($attendance);

        return $attendance;
    }

    /**
     * Get today's attendance status for a user.
     */
    public function getTodayStatus(User $user): ?Attendance
    {
        return Attendance::where('user_id', $user->id)
            ->where('date', Carbon::today()->toDateString())
            ->first();
    }

    /**
     * Get weekly timesheet for a user.
     */
    /**
     * Get attendance records for a date range.
     */
    public function getAttendanceRecords(User $user, $startDate, $endDate)
    {
        $settings = \App\Models\BusinessSetting::whereIn('key', ['shift_start_time', 'shift_end_time', 'timezone'])->pluck('value', 'key');
        $shiftStart = $settings['shift_start_time'] ?? '09:00';
        $shiftEnd = $settings['shift_end_time'] ?? '18:00';
        $appTimezone = $settings['timezone'] ?? config('app.timezone');

        $attendances = Attendance::where('user_id', $user->id)
            ->whereBetween('date', [$startDate->toDateString(), $endDate->toDateString()])
            ->orderBy('date', 'desc')
            ->get();

        $attendances->transform(function ($attendance) use ($shiftStart, $shiftEnd, $appTimezone) {
            $date = \Carbon\Carbon::parse($attendance->date)->format('Y-m-d');

            $attendance->is_late = false;
            $attendance->is_early_out = false;
            $attendance->late_time = null;
            $attendance->early_out_time = null;

            if ($attendance->clock_in) {
                $shiftStartDateTime = Carbon::parse($date.' '.$shiftStart, $appTimezone);
                $clockInTime = Carbon::parse($attendance->clock_in)->setTimezone($appTimezone);

                if ($clockInTime->gt($shiftStartDateTime->copy()->addMinutes(5))) {
                    $attendance->is_late = true;
                    $diffInMinutes = $clockInTime->diffInMinutes($shiftStartDateTime);
                    $h = floor($diffInMinutes / 60);
                    $m = $diffInMinutes % 60;
                    $attendance->late_time = "{$h}h {$m}m";
                }
            }

            if ($attendance->clock_out) {
                $shiftEndDateTime = Carbon::parse($date.' '.$shiftEnd, $appTimezone);
                $clockOutTime = Carbon::parse($attendance->clock_out)->setTimezone($appTimezone);

                if ($clockOutTime->lt($shiftEndDateTime->subMinutes(1))) {
                    $attendance->is_early_out = true;
                    $cleanShiftEnd = Carbon::parse($date.' '.$shiftEnd, $appTimezone);
                    $diffInMinutes = $cleanShiftEnd->diffInMinutes($clockOutTime);
                    $h = floor($diffInMinutes / 60);
                    $m = $diffInMinutes % 60;
                    $attendance->early_out_time = "{$h}h {$m}m";
                }
            }

            return $attendance;
        });

        return $attendances;
    }

    /**
     * Get log history for dashboard (filtered by date or default to week).
     */
    public function getLogHistory(User $user, ?string $date = null)
    {
        if ($date) {
            $start = Carbon::parse($date);
            $end = Carbon::parse($date);
        } else {
            $start = Carbon::now()->startOfWeek();
            $end = Carbon::now()->endOfWeek();
        }

        return $this->getAttendanceRecords($user, $start, $end);
    }

    /**
     * Get weekly timesheet for a user (Legacy/Specific for Weekly View).
     */
    public function getWeeklyTimesheet(User $user, $startDate = null)
    {
        $start = $startDate ? Carbon::parse($startDate)->startOfWeek() : Carbon::now()->startOfWeek();
        $end = $start->copy()->endOfWeek();

        return $this->getAttendanceRecords($user, $start, $end);
    }

    public function resolveDailyStatus(?Attendance $attendance, bool $isOnApprovedLeave, string $date): string
    {
        if ($isOnApprovedLeave) {
            return 'leave';
        }

        if (! $attendance || ! $attendance->clock_in) {
            return 'absent';
        }

        return $this->isLateForDate($attendance->clock_in, $date) ? 'late' : 'present';
    }

    /**
     * Get dashboard stats for a user.
     */
    public function getDashboardStats(User $user): array
    {
        $now = Carbon::now();
        $startOfMonth = $now->copy()->startOfMonth();
        $endOfMonth = $now->copy()->endOfMonth();

        $monthAttendances = Attendance::where('user_id', $user->id)
            ->whereBetween('date', [$startOfMonth->toDateString(), $endOfMonth->toDateString()])
            ->get();

        $settings = \App\Models\BusinessSetting::whereIn('key', ['shift_start_time', 'monthly_attendance_goal', 'timezone'])->pluck('value', 'key');
        $shiftStart = $settings['shift_start_time'] ?? '09:00';
        $monthlyGoal = $settings['monthly_attendance_goal'] ?? 160;
        $appTimezone = $settings['timezone'] ?? config('app.timezone');

        $totalDays = $now->day; // Assuming working days roughly map to calendar days for simple rate calc, or use business days
        // A better approach for attendance rate: Days present / Working days so far.
        // For simplicity, let's use: Days present / Days passed in month excluding weekends if possible, or just raw count.
        // Let's stick to the requested UI stats.

        $presentCount = $monthAttendances->count();
        $attendanceRate = $totalDays > 0 ? round(($presentCount / $totalDays) * 100, 1) : 100;

        // Average Check In Time
        $totalCheckInSeconds = 0;
        $checkInCount = 0;
        foreach ($monthAttendances as $attendance) {
            if ($attendance->clock_in) {
                // Convert to App Timezone for average calculation so it makes sense to user
                $totalCheckInSeconds += Carbon::parse($attendance->clock_in)->setTimezone($appTimezone)->secondsSinceMidnight();
                $checkInCount++;
            }
        }
        $avgCheckIn = $checkInCount > 0 ? gmdate('h:i A', $totalCheckInSeconds / $checkInCount) : '--:--';

        // Late Counts
        $lateCount = $monthAttendances->filter(function ($att) use ($shiftStart, $appTimezone) {
            if (! $att->clock_in) {
                return false;
            }

            $date = \Carbon\Carbon::parse($att->date)->format('Y-m-d');
            $shiftStartDateTime = Carbon::parse($date.' '.$shiftStart, $appTimezone);
            $clockInTime = Carbon::parse($att->clock_in)->setTimezone($appTimezone);

            // Allow 5 minutes grace period
            // return $clockInTime->format('H:i:s') > $shiftStartDateTime->addMinutes(5)->format('H:i:s'); // Simple string comparison or diff
            return $clockInTime->gt($shiftStartDateTime->addMinutes(5));
        })->count();

        // Total Overtime
        $totalOvertime = $monthAttendances->sum('overtime_hours');

        // Monthly Goal
        $totalHoursWorked = $monthAttendances->sum('total_hours');
        $goalProgress = $monthlyGoal > 0 ? round(($totalHoursWorked / $monthlyGoal) * 100, 1) : 0;

        return [
            'attendance_rate' => $attendanceRate,
            'avg_check_in' => $avgCheckIn,
            'total_overtime' => $totalOvertime,
            'late_count' => $lateCount,
            'monthly_goal' => [
                'target' => (int) $monthlyGoal,
                'current' => (float) $totalHoursWorked,
                'progress' => $goalProgress,
            ],
        ];
    }

    /**
     * Approve or reject attendance.
     */
    public function updateStatus(Attendance $attendance, string $status, User $admin): Attendance
    {
        $attendance->update([
            'status' => $status,
            'approved_by' => $admin->id,
        ]);

        return $attendance;
    }

    /**
     * Get all pending manual attendances for review.
     */
    public function getPendingReviews()
    {
        return Attendance::with('user.employeeProfile')
            ->where('status', 'pending')
            ->orderBy('date', 'desc')
            ->get();
    }

    /**
     * Store a manual attendance entry.
     */
    public function storeManualEntry(User $user, string $date, string $clockIn, ?string $clockOut = null, ?string $note = null): Attendance
    {
        $clockInDateTime = Carbon::parse($date.' '.$clockIn);
        $clockOutDateTime = $clockOut ? Carbon::parse($date.' '.$clockOut) : null;

        $totalHours = 0;
        if ($clockInDateTime && $clockOutDateTime) {
            $diffInSeconds = $clockInDateTime->diffInSeconds($clockOutDateTime);
            $totalHours = $diffInSeconds / 3600;
        }

        return Attendance::updateOrCreate(
            ['user_id' => $user->id, 'date' => $date],
            [
                'clock_in' => $clockInDateTime,
                'clock_out' => $clockOutDateTime,
                'in_type' => 'manual',
                'out_type' => $clockOut ? 'manual' : 'auto',
                'in_note' => $note,
                'total_hours' => round(max(0, $totalHours), 2),
                'status' => 'pending',
                'work_mode' => 'office', // Default for manual entries unless specified
            ]
        );
    }

    /**
     * Detect and flag attendance anomalies (late/early).
     */
    protected function detectAndFlagAnomalies(Attendance $attendance): void
    {
        $settings = \App\Models\BusinessSetting::whereIn('key', ['shift_start_time', 'shift_end_time', 'timezone'])->pluck('value', 'key');
        $shiftStart = $settings['shift_start_time'] ?? '09:00';
        $shiftEnd = $settings['shift_end_time'] ?? '18:00';
        $timezone = $settings['timezone'] ?? config('app.timezone');

        $date = Carbon::parse($attendance->date)->format('Y-m-d');
        $shiftStartDateTime = Carbon::parse($date.' '.$shiftStart, $timezone);
        $shiftEndDateTime = Carbon::parse($date.' '.$shiftEnd, $timezone);

        $isLate = false;
        $isEarly = false;

        // Check for late check-in (5 minutes grace period)
        if ($attendance->clock_in) {
            $clockInTime = Carbon::parse($attendance->clock_in)->setTimezone($timezone);
            if ($clockInTime->gt($shiftStartDateTime->copy()->addMinutes(5))) {
                $isLate = true;
            }
        }

        // Check for early check-out (1 minute before shift end)
        if ($attendance->clock_out) {
            $clockOutTime = Carbon::parse($attendance->clock_out)->setTimezone($timezone);
            if ($clockOutTime->lt($shiftEndDateTime->copy()->subMinute())) {
                $isEarly = true;
            }
        }

        // Route anomalies through manager first when configured; otherwise direct to admin.
        if ($isLate || $isEarly) {
            $manager = $attendance->user->employeeProfile?->manager;

            if ($manager) {
                $attendance->update(['anomaly_status' => 'pending_manager']);
                $this->notifyManager($attendance);
            } else {
                $attendance->update(['anomaly_status' => 'pending_admin']);
                $this->notifyAdmin($attendance, false);
            }
        }
    }

    protected function isLateForDate(\DateTimeInterface|string $clockIn, string $date): bool
    {
        $settings = \App\Models\BusinessSetting::whereIn('key', ['shift_start_time', 'timezone'])->pluck('value', 'key');
        $shiftStart = $settings['shift_start_time'] ?? '09:00';
        $timezone = $settings['timezone'] ?? config('app.timezone');

        $shiftStartDateTime = Carbon::parse($date.' '.$shiftStart, $timezone);
        $clockInTime = Carbon::parse($clockIn)->setTimezone($timezone);

        return $clockInTime->gt($shiftStartDateTime->addMinutes(5));
    }

    /**
     * Notify manager of attendance anomaly.
     */
    protected function notifyManager(Attendance $attendance): void
    {
        $manager = $attendance->user->employeeProfile?->manager;

        if (! $manager) {
            return;
        }

        $anomalyType = $this->getAnomalyType($attendance);

        $this->notificationService->warning(
            $manager->id,
            'Attendance Anomaly Detected',
            "{$attendance->user->name} has a {$anomalyType} on ".Carbon::parse($attendance->date)->format('M d, Y'),
            ['attendance_id' => $attendance->id],
            '/employee/manager/attendance/approvals'
        );
    }

    /**
     * Get anomaly type for an attendance record.
     */
    protected function getAnomalyType(Attendance $attendance): string
    {
        $settings = \App\Models\BusinessSetting::whereIn('key', ['shift_start_time', 'shift_end_time', 'timezone'])->pluck('value', 'key');
        $shiftStart = $settings['shift_start_time'] ?? '09:00';
        $shiftEnd = $settings['shift_end_time'] ?? '18:00';
        $timezone = $settings['timezone'] ?? config('app.timezone');

        $date = Carbon::parse($attendance->date)->format('Y-m-d');
        $shiftStartDateTime = Carbon::parse($date.' '.$shiftStart, $timezone);
        $shiftEndDateTime = Carbon::parse($date.' '.$shiftEnd, $timezone);

        $anomalies = [];

        if ($attendance->clock_in) {
            $clockInTime = Carbon::parse($attendance->clock_in)->setTimezone($timezone);
            if ($clockInTime->gt($shiftStartDateTime->copy()->addMinutes(5))) {
                $anomalies[] = 'Late Check-in';
            }
        }

        if ($attendance->clock_out) {
            $clockOutTime = Carbon::parse($attendance->clock_out)->setTimezone($timezone);
            if ($clockOutTime->lt($shiftEndDateTime->copy()->subMinute())) {
                $anomalies[] = 'Early Check-out';
            }
        }

        return implode(' & ', $anomalies);
    }

    /**
     * Get anomaly duration (how late or how early).
     */
    public function getAnomalyDuration(Attendance $attendance): array
    {
        $settings = \App\Models\BusinessSetting::whereIn('key', ['shift_start_time', 'shift_end_time', 'timezone'])->pluck('value', 'key');
        $shiftStart = $settings['shift_start_time'] ?? '09:00';
        $shiftEnd = $settings['shift_end_time'] ?? '18:00';
        $timezone = $settings['timezone'] ?? config('app.timezone');

        $date = Carbon::parse($attendance->date)->format('Y-m-d');
        $shiftStartDateTime = Carbon::parse($date.' '.$shiftStart, $timezone);
        $shiftEndDateTime = Carbon::parse($date.' '.$shiftEnd, $timezone);

        $result = [];

        if ($attendance->clock_in) {
            $clockInTime = Carbon::parse($attendance->clock_in)->setTimezone($timezone);
            if ($clockInTime->gt($shiftStartDateTime->copy()->addMinutes(5))) {
                $lateMinutes = $clockInTime->diffInMinutes($shiftStartDateTime);
                $result['late_duration'] = $this->formatDuration($lateMinutes);
                $result['late_minutes'] = $lateMinutes;
            }
        }

        if ($attendance->clock_out) {
            $clockOutTime = Carbon::parse($attendance->clock_out)->setTimezone($timezone);
            if ($clockOutTime->lt($shiftEndDateTime->copy()->subMinute())) {
                $earlyMinutes = $shiftEndDateTime->diffInMinutes($clockOutTime);
                $result['early_duration'] = $this->formatDuration($earlyMinutes);
                $result['early_minutes'] = $earlyMinutes;
            }
        }

        return $result;
    }

    /**
     * Format duration in human-readable format.
     */
    protected function formatDuration(int $minutes): string
    {
        $hours = floor($minutes / 60);
        $remainingMinutes = $minutes % 60;

        if ($hours === 0) {
            return '0 hr '.$remainingMinutes.' min';
        }

        if ($remainingMinutes === 0) {
            return $hours.' hr 0 min';
        }

        return $hours.' hr '.$remainingMinutes.' min';
    }

    /**
     * Manager approves or rejects attendance anomaly.
     */
    public function approveByManager(Attendance $attendance, User $manager, bool $approve, ?string $reason = null): bool
    {
        if ($attendance->anomaly_status !== 'pending_manager') {
            return false;
        }

        $attendance->update([
            'anomaly_status' => $approve ? 'pending_admin' : 'rejected_manager',
            'manager_approved_by' => $manager->id,
            'manager_approved_at' => now(),
            'anomaly_reason' => $reason,
        ]);

        if ($approve) {
            // Notify admin
            $this->notifyAdmin($attendance);
        } else {
            // Notify employee of rejection
            $this->notificationService->warning(
                $attendance->user_id,
                'Attendance Anomaly Rejected',
                'Your attendance anomaly on '.Carbon::parse($attendance->date)->format('M d, Y').' was rejected by your manager.'.($reason ? " Reason: {$reason}" : ''),
                ['attendance_id' => $attendance->id],
                '/employee/attendance'
            );
        }

        return true;
    }

    /**
     * Admin approves or rejects attendance anomaly.
     */
    public function approveByAdmin(Attendance $attendance, User $admin, bool $approve, ?string $reason = null): bool
    {
        if ($attendance->anomaly_status !== 'pending_admin') {
            return false;
        }

        $attendance->update([
            'anomaly_status' => $approve ? 'approved_admin' : 'rejected_admin',
            'admin_approved_by' => $admin->id,
            'admin_approved_at' => now(),
            'anomaly_reason' => $reason,
        ]);

        // Notify employee
        $message = $approve
            ? 'Your attendance anomaly on '.Carbon::parse($attendance->date)->format('M d, Y').' has been approved.'
            : 'Your attendance anomaly on '.Carbon::parse($attendance->date)->format('M d, Y').' was rejected.'.($reason ? " Reason: {$reason}" : '');

        $this->notificationService->{$approve ? 'info' : 'warning'}(
            $attendance->user_id,
            $approve ? 'Attendance Anomaly Approved' : 'Attendance Anomaly Rejected',
            $message,
            ['attendance_id' => $attendance->id],
            '/employee/attendance'
        );

        // Notify manager
        if ($attendance->manager_approved_by) {
            $this->notificationService->info(
                $attendance->manager_approved_by,
                'Attendance Anomaly '.($approve ? 'Approved' : 'Rejected'),
                "The attendance anomaly for {$attendance->user->name} on ".Carbon::parse($attendance->date)->format('M d, Y').' has been '.($approve ? 'approved' : 'rejected').' by admin.',
                ['attendance_id' => $attendance->id],
                '/manager/attendance/approvals'
            );
        }

        return true;
    }

    /**
     * Notify admin of attendance anomaly pending approval.
     */
    protected function notifyAdmin(Attendance $attendance, bool $approvedByManager = true): void
    {
        // Get all admin and super admin users
        $admins = User::whereIn('type', [\App\Enums\UserType::ADMIN, \App\Enums\UserType::SUPER_ADMIN])->get();

        $anomalyType = $this->getAnomalyType($attendance);
        $message = $approvedByManager
            ? "{$attendance->user->name} has a {$anomalyType} on ".Carbon::parse($attendance->date)->format('M d, Y').' (Approved by Manager)'
            : "{$attendance->user->name} has a {$anomalyType} on ".Carbon::parse($attendance->date)->format('M d, Y').' and has no reporting manager. Pending direct Admin approval.';

        foreach ($admins as $admin) {
            $this->notificationService->info(
                $admin->id,
                'Attendance Anomaly Needs Approval',
                $message,
                ['attendance_id' => $attendance->id],
                '/admin/attendance/approvals'
            );
        }
    }

    /**
     * Get pending approvals for a manager.
     */
    public function getManagerPendingApprovals(User $manager): \Illuminate\Database\Eloquent\Collection
    {
        // Get all employees reporting to this manager
        $employeeIds = \App\Models\EmployeeProfile::where('manager_id', $manager->id)->pluck('user_id');

        $approvals = Attendance::whereIn('user_id', $employeeIds)
            ->where('anomaly_status', 'pending_manager')
            ->with(['user', 'user.employeeProfile'])
            ->orderBy('date', 'desc')
            ->get();

        // Add anomaly duration to each approval
        return $approvals->map(function ($attendance) {
            $attendance->anomaly_duration = $this->getAnomalyDuration($attendance);
            $attendance->anomaly_type = $this->getAnomalyType($attendance);

            return $attendance;
        });
    }

    /**
     * Get pending approvals for admin.
     */
    public function getAdminPendingApprovals(): \Illuminate\Database\Eloquent\Collection
    {
        $approvals = Attendance::where('anomaly_status', 'pending_admin')
            ->with(['user', 'user.employeeProfile', 'managerApprovedBy'])
            ->orderBy('date', 'desc')
            ->get();

        // Add anomaly duration to each approval
        return $approvals->map(function ($attendance) {
            $attendance->anomaly_duration = $this->getAnomalyDuration($attendance);
            $attendance->anomaly_type = $this->getAnomalyType($attendance);

            return $attendance;
        });
    }
}
