<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Models\AttendanceLog;
use App\Models\AttendanceSetting;
use App\Models\Leave;
use App\Models\User;
use App\Notifications\SystemAlertNotification;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;

class AttendanceController extends Controller
{
    private const EARLY_CHECK_IN_WINDOW_MINUTES = 60;

    private const LATE_ABSENT_THRESHOLD_MINUTES = 30;

    public function settings(Request $request)
    {
        if (! Schema::hasTable('attendance_settings')) {
            return response()->json([
                'message' => 'Attendance settings not configured',
            ], 404);
        }

        $settings = AttendanceSetting::query()->first();

        if (! $settings || $settings->latitude === null || $settings->longitude === null) {
            return response()->json([
                'message' => 'Attendance settings not configured',
            ], 404);
        }

        return response()->json([
            'latitude' => $settings->latitude,
            'longitude' => $settings->longitude,
            'allowed_radius' => $settings->allowed_radius,
        ]);
    }

    public function todayStatus(Request $request)
    {
        $user = $request->user();
        $range = $this->todayRange();

        $logs = AttendanceLog::query()
            ->where('user_id', $user->id)
            ->whereBetween('logged_at', [$range['start'], $range['end']])
            ->orderBy('logged_at')
            ->get();

        $checkIn = $logs->firstWhere('type', 'check_in');
        $checkOut = $logs->firstWhere('type', 'check_out');

        $status = 'none';
        if ($checkIn && ! $checkOut) {
            $status = 'working';
        } elseif ($checkIn && $checkOut) {
            $status = 'checked_out';
        }

        return response()->json([
            'status' => $status,
            'check_in_at' => $checkIn?->logged_at?->toIso8601String(),
            'check_out_at' => $checkOut?->logged_at?->toIso8601String(),
        ]);
    }

    public function checkIn(Request $request)
    {
        $user = $request->user();
        $validated = $request->validate([
            'device_name' => 'nullable|string|max:255',
            'device_platform' => 'nullable|string|max:50',
            'logged_at' => 'nullable|date',
            'image' => 'required|image|max:4096',
            'latitude' => 'nullable|numeric',
            'longitude' => 'nullable|numeric',
            'notes' => 'nullable|string',
        ]);

        $range = $this->todayRange();

        $existingCheckIn = AttendanceLog::query()
            ->where('user_id', $user->id)
            ->where('type', 'check_in')
            ->whereBetween('logged_at', [$range['start'], $range['end']])
            ->first();

        if ($existingCheckIn) {
            return response()->json([
                'message' => 'تم تسجيل الدخول مسبقاً اليوم',
            ], 409);
        }

        $user->loadMissing('employee');

        if (! $user->employee) {
            return response()->json([
                'message' => 'لا يوجد موظف مرتبط بهذا المستخدم',
            ], 409);
        }

        $isProgrammer = (bool) ($user->employee->is_programmer ?? false);
        $programmerHours = max(0, min(24, (int) ($user->employee->programmer_work_hours ?? 0)));

        if (! $isProgrammer) {
            if (! $user->employee->attendance_time || ! $user->employee->departure_time) {
                return response()->json([
                    'message' => 'لم يتم ضبط وقت الحضور/الانصراف لهذا الموظف',
                ], 409);
            }
        } else {
            if ($programmerHours <= 0) {
                return response()->json([
                    'message' => 'لم يتم ضبط عدد ساعات الدوام لهذا الموظف',
                ], 409);
            }
        }

        $leaveMessage = $this->approvedLeaveTodayErrorMessage($user, 'Asia/Baghdad');
        if ($leaveMessage !== null) {
            return response()->json([
                'message' => $leaveMessage,
            ], 422);
        }

        $locationMessage = $this->checkInLocationErrorMessage($user->employee, $validated);
        if ($locationMessage !== null) {
            return response()->json([
                'message' => $locationMessage,
            ], 422);
        }

        $loggedAt = isset($validated['logged_at'])
            ? Carbon::parse($validated['logged_at'])->utc()
            : Carbon::now('UTC');

        if ($isProgrammer) {
            $loggedAtLocal = $loggedAt->copy()->timezone('Asia/Baghdad');
            $scheduledAttendance = $loggedAtLocal->format('H:i:s');
            $scheduledDeparture = $loggedAtLocal->copy()->addHours($programmerHours)->format('H:i:s');
            $metrics = [
                'scheduled_attendance_time' => $scheduledAttendance,
                'scheduled_departure_time' => $scheduledDeparture,
                'early_minutes' => null,
                'late_minutes' => null,
                'attendance_status' => 'present',
            ];
        } else {
            $allowGracePeriod = (bool) ($user->employee->allow_grace_period ?? false);
            $graceMinutes = (int) ($user->employee->grace_minutes ?? 0);

            $metrics = $this->computeCheckInMetrics(
                $loggedAt,
                (string) $user->employee->attendance_time,
                (string) $user->employee->departure_time,
                'Asia/Baghdad',
                $allowGracePeriod,
                $graceMinutes
            );

            if (isset($metrics['error_message'])) {
                return response()->json([
                    'message' => $metrics['error_message'],
                ], 422);
            }
        }

        $datePath = $loggedAt->copy()->timezone('Asia/Baghdad')->format('Y/m/d');
        $dir = "attendance/{$datePath}/user_{$user->id}";
        $imagePath = $request->file('image')->storePublicly($dir, 'public');

        $log = AttendanceLog::create([
            'user_id' => $user->id,
            'employee_id' => $user->employee->id,
            'type' => 'check_in',
            'logged_at' => $loggedAt,
            'scheduled_attendance_time' => $metrics['scheduled_attendance_time'],
            'scheduled_departure_time' => $metrics['scheduled_departure_time'],
            'early_minutes' => $metrics['early_minutes'],
            'late_minutes' => $metrics['late_minutes'],
            'attendance_status' => $metrics['attendance_status'],
            'latitude' => $validated['latitude'] ?? null,
            'longitude' => $validated['longitude'] ?? null,
            'notes' => $validated['notes'] ?? null,
            'image_path' => $imagePath,
            'device_name' => $validated['device_name'] ?? null,
            'device_platform' => $validated['device_platform'] ?? null,
        ]);

        $this->notifyAdminsForAttendance($user, $log, 'check_in');

        return response()->json([
            'message' => 'تم تسجيل الدخول بنجاح',
            'log' => $this->serializeLog($log),
        ]);
    }

    public function checkOut(Request $request)
    {
        $user = $request->user();
        $validated = $request->validate([
            'device_name' => 'nullable|string|max:255',
            'device_platform' => 'nullable|string|max:50',
            'logged_at' => 'nullable|date',
            'latitude' => 'nullable|numeric',
            'longitude' => 'nullable|numeric',
            'notes' => 'nullable|string',
        ]);

        $range = $this->todayRange();

        $checkIn = AttendanceLog::query()
            ->where('user_id', $user->id)
            ->where('type', 'check_in')
            ->whereBetween('logged_at', [$range['start'], $range['end']])
            ->orderBy('logged_at')
            ->first();

        if (! $checkIn) {
            return response()->json([
                'message' => 'لم يتم تسجيل دخول اليوم',
            ], 409);
        }

        $existingCheckOut = AttendanceLog::query()
            ->where('user_id', $user->id)
            ->where('type', 'check_out')
            ->whereBetween('logged_at', [$range['start'], $range['end']])
            ->first();

        if ($existingCheckOut) {
            return response()->json([
                'message' => 'تم تسجيل الخروج مسبقاً اليوم',
            ], 409);
        }

        $user->loadMissing('employee');

        if (! $user->employee) {
            return response()->json([
                'message' => 'لا يوجد موظف مرتبط بهذا المستخدم',
            ], 409);
        }

        $isProgrammer = (bool) ($user->employee->is_programmer ?? false);
        $programmerHours = max(0, min(24, (int) ($user->employee->programmer_work_hours ?? 0)));

        if (! $isProgrammer) {
            if (! $user->employee->departure_time) {
                return response()->json([
                    'message' => 'لم يتم ضبط وقت الانصراف لهذا الموظف',
                ], 409);
            }
        } else {
            if ($programmerHours <= 0) {
                return response()->json([
                    'message' => 'لم يتم ضبط عدد ساعات الدوام لهذا الموظف',
                ], 409);
            }
        }

        $loggedAt = isset($validated['logged_at'])
            ? Carbon::parse($validated['logged_at'])->utc()
            : Carbon::now('UTC');

        $hasApprovedLeaveToday = $this->hasApprovedLeaveToday($user, 'Asia/Baghdad');
        if (! $hasApprovedLeaveToday) {
            if ($isProgrammer) {
                $tz = 'Asia/Baghdad';
                $checkInLocal = $checkIn->logged_at?->copy()->timezone($tz);
                if ($checkInLocal) {
                    $minCheckoutLocal = $checkInLocal->copy()->addHours($programmerHours);
                    $loggedAtLocal = $loggedAt->copy()->timezone($tz);
                    if ($loggedAtLocal->lessThan($minCheckoutLocal)) {
                        return response()->json([
                            'message' => 'لا يسمح بتسجيل الانصراف قبل إكمال عدد ساعات الدوام المحددة',
                        ], 422);
                    }
                }
            } else {
                $earlyCheckoutMessage = $this->earlyCheckoutErrorMessage($loggedAt, (string) $user->employee->departure_time, 'Asia/Baghdad');
                if ($earlyCheckoutMessage !== null) {
                    return response()->json([
                        'message' => $earlyCheckoutMessage,
                    ], 422);
                }
            }
        }

        $log = AttendanceLog::create([
            'user_id' => $user->id,
            'employee_id' => $user->employee->id,
            'type' => 'check_out',
            'logged_at' => $loggedAt,
            'scheduled_departure_time' => $isProgrammer ? ($checkIn->scheduled_departure_time ?: null) : $this->normalizeTime((string) $user->employee->departure_time),
            'latitude' => $validated['latitude'] ?? null,
            'longitude' => $validated['longitude'] ?? null,
            'notes' => $validated['notes'] ?? null,
            'device_name' => $validated['device_name'] ?? null,
            'device_platform' => $validated['device_platform'] ?? null,
        ]);

        $this->notifyAdminsForAttendance($user, $log, 'check_out');

        return response()->json([
            'message' => 'تم تسجيل الخروج بنجاح',
            'log' => $this->serializeLog($log),
        ]);
    }

    private function notifyAdminsForAttendance($user, AttendanceLog $log, string $type): void
    {
        if (! Schema::hasTable('notifications')) {
            return;
        }

        $title = $type === 'check_out' ? 'تسجيل خروج' : 'تسجيل حضور';
        $tz = 'Asia/Baghdad';
        $time = $log->logged_at?->timezone($tz)?->format('H:i') ?? '';
        $date = $log->logged_at?->timezone($tz)?->format('Y-m-d') ?? '';

        $name = $user?->employee?->full_name ?? $user?->name ?? '—';
        $body = trim($name).' - '.$date.($time ? (' '.$time) : '');

        $admins = User::query()
            ->where('is_admin', true)
            ->orWhereRaw('LOWER(username) = ?', ['admin'])
            ->orWhereHas('roles', fn ($q) => $q->whereIn('key', ['super_admin', 'admin']))
            ->get();

        foreach ($admins as $admin) {
            $admin->notify(new SystemAlertNotification($title, $body, [
                'event' => $type === 'check_out' ? 'attendance_check_out' : 'attendance_check_in',
                'attendance_log_id' => $log->id,
                'employee_id' => $log->employee_id,
                'user_id' => $log->user_id,
            ]));
        }
    }

    public function today(Request $request)
    {
        $user = $request->user();
        $range = $this->todayRange();

        $logs = AttendanceLog::query()
            ->where('user_id', $user->id)
            ->whereBetween('logged_at', [$range['start'], $range['end']])
            ->orderBy('logged_at')
            ->get()
            ->map(fn (AttendanceLog $log) => $this->serializeLog($log))
            ->values();

        return response()->json([
            'logs' => $logs,
        ]);
    }

    public function history(Request $request)
    {
        $user = $request->user();
        $validated = $request->validate([
            'from' => 'nullable|date',
            'to' => 'nullable|date',
        ]);

        $range = $this->historyRange(
            $validated['from'] ?? null,
            $validated['to'] ?? null,
            'Asia/Baghdad'
        );

        $logs = AttendanceLog::query()
            ->where('user_id', $user->id)
            ->whereBetween('logged_at', [$range['start'], $range['end']])
            ->orderByDesc('logged_at')
            ->get()
            ->map(fn (AttendanceLog $log) => $this->serializeLog($log))
            ->values();

        return response()->json([
            'logs' => $logs,
        ]);
    }

    public function reportSummary(Request $request)
    {
        $user = $request->user();
        $validated = $request->validate([
            'date' => 'nullable|date',
        ]);

        $tz = 'Asia/Baghdad';
        $selectedDate = isset($validated['date']) && trim((string) $validated['date']) !== ''
            ? Carbon::parse($validated['date'], $tz)
            : Carbon::now($tz);

        $monthStartLocal = $selectedDate->copy()->startOfMonth()->startOfDay();
        $monthEndLocal = $selectedDate->copy()->endOfMonth()->endOfDay();
        $monthStartUtc = $monthStartLocal->copy()->setTimezone('UTC');
        $monthEndUtc = $monthEndLocal->copy()->setTimezone('UTC');

        $dayStartUtc = $selectedDate->copy()->startOfDay()->setTimezone('UTC');
        $dayEndUtc = $selectedDate->copy()->endOfDay()->setTimezone('UTC');

        $monthLateMinutes = (int) AttendanceLog::query()
            ->where('user_id', $user->id)
            ->where('type', 'check_in')
            ->whereBetween('logged_at', [$monthStartUtc, $monthEndUtc])
            ->sum('late_minutes');

        $dayLateMinutes = (int) AttendanceLog::query()
            ->where('user_id', $user->id)
            ->where('type', 'check_in')
            ->whereBetween('logged_at', [$dayStartUtc, $dayEndUtc])
            ->sum('late_minutes');

        $monthFullDayLeaves = (int) Leave::query()
            ->where('user_id', $user->id)
            ->where('leave_type', 'full_day')
            ->whereBetween('leave_date', [$monthStartLocal->toDateString(), $monthEndLocal->toDateString()])
            ->count();

        $monthTimedLeaves = (int) Leave::query()
            ->where('user_id', $user->id)
            ->where('leave_type', 'timed')
            ->whereBetween('leave_date', [$monthStartLocal->toDateString(), $monthEndLocal->toDateString()])
            ->count();

        $dayFullDayLeaves = (int) Leave::query()
            ->where('user_id', $user->id)
            ->where('leave_type', 'full_day')
            ->whereDate('leave_date', $selectedDate->toDateString())
            ->count();

        $dayTimedLeaves = (int) Leave::query()
            ->where('user_id', $user->id)
            ->where('leave_type', 'timed')
            ->whereDate('leave_date', $selectedDate->toDateString())
            ->count();

        $settings = Schema::hasTable('attendance_settings') ? AttendanceSetting::query()->first() : null;
        $lateMinutePrice = (int) ($settings?->late_minute_price ?? 0);

        $monthLateAmount = 0;
        $dayLateAmount = 0;

        if ($lateMinutePrice > 0) {
            $monthLateAmount = $monthLateMinutes * $lateMinutePrice;
            $dayLateAmount = $dayLateMinutes * $lateMinutePrice;
        } else {
            $user->loadMissing('employee');
            $minuteRate = $user->employee?->minuteRate(\App\Models\Employee::DEFAULT_SALARY_WORKING_DAYS_DIVISOR, $tz) ?? 0;
            $monthLateAmount = (int) round($monthLateMinutes * $minuteRate);
            $dayLateAmount = (int) round($dayLateMinutes * $minuteRate);
        }

        return response()->json([
            'month' => $selectedDate->format('Y-m'),
            'selected_date' => $selectedDate->toDateString(),
            'month_late_minutes' => $monthLateMinutes,
            'month_late_amount' => $monthLateAmount,
            'month_full_day_leaves' => $monthFullDayLeaves,
            'month_timed_leaves' => $monthTimedLeaves,
            'day_late_minutes' => $dayLateMinutes,
            'day_late_amount' => $dayLateAmount,
            'day_full_day_leaves' => $dayFullDayLeaves,
            'day_timed_leaves' => $dayTimedLeaves,
        ]);
    }

    private function todayRange(): array
    {
        $tz = 'Asia/Baghdad';
        $now = Carbon::now($tz);

        return [
            'start' => $now->copy()->startOfDay()->setTimezone('UTC'),
            'end' => $now->copy()->endOfDay()->setTimezone('UTC'),
        ];
    }

    private function historyRange(?string $from, ?string $to, string $tz): array
    {
        $now = Carbon::now($tz);

        $fromDate = $from ? Carbon::parse($from, $tz) : $now->copy()->startOfMonth();
        $toDate = $to ? Carbon::parse($to, $tz) : $now->copy()->endOfMonth();

        return [
            'start' => $fromDate->copy()->startOfDay()->setTimezone('UTC'),
            'end' => $toDate->copy()->endOfDay()->setTimezone('UTC'),
        ];
    }

    private function serializeLog(AttendanceLog $log): array
    {
        $imageUrl = $log->image_path ? url('storage/'.$log->image_path) : null;

        return [
            'id' => $log->id,
            'user_id' => $log->user_id,
            'employee_id' => $log->employee_id,
            'type' => $log->type,
            'logged_at' => $log->logged_at?->toIso8601String(),
            'scheduled_attendance_time' => $log->scheduled_attendance_time,
            'scheduled_departure_time' => $log->scheduled_departure_time,
            'early_minutes' => $log->early_minutes,
            'late_minutes' => $log->late_minutes,
            'attendance_status' => $log->attendance_status,
            'latitude' => $log->latitude,
            'longitude' => $log->longitude,
            'notes' => $log->notes,
            'image_path' => $log->image_path,
            'image_url' => $imageUrl,
            'device_name' => $log->device_name,
            'device_platform' => $log->device_platform,
        ];
    }

    private function checkInLocationErrorMessage($employee, array $validated): ?string
    {
        $lat = $validated['latitude'] ?? null;
        $lng = $validated['longitude'] ?? null;

        if ($lat === null || $lng === null) {
            return 'يجب إرسال latitude و longitude عند تسجيل الحضور';
        }

        if ((bool) ($employee->allow_outside_company ?? false)) {
            return null;
        }

        if (! Schema::hasTable('attendance_settings')) {
            return 'لم يتم ضبط إعدادات الحضور الخاصة بموقع الشركة';
        }

        $settings = AttendanceSetting::query()->first();
        if (! $settings || $settings->latitude === null || $settings->longitude === null || ! $settings->allowed_radius) {
            return 'لم يتم ضبط إعدادات الحضور الخاصة بموقع الشركة';
        }

        $distanceMeters = $this->distanceMeters((float) $lat, (float) $lng, (float) $settings->latitude, (float) $settings->longitude);
        if ($distanceMeters > (int) $settings->allowed_radius) {
            return 'أنت خارج نطاق الشركة المسموح به لتسجيل الحضور';
        }

        return null;
    }

    private function approvedLeaveTodayErrorMessage($user, string $tz): ?string
    {
        return $this->hasApprovedLeaveToday($user, $tz) ? 'أنت مجاز لهذا اليوم' : null;
    }

    private function hasApprovedLeaveToday($user, string $tz): bool
    {
        if (! Schema::hasTable('leaves')) {
            return false;
        }

        $today = Carbon::now($tz)->toDateString();

        return Leave::query()
            ->where('user_id', $user->id)
            ->where('status', 'approved')
            ->whereDate('leave_date', $today)
            ->exists();
    }

    private function distanceMeters(float $lat1, float $lon1, float $lat2, float $lon2): float
    {
        $earthRadius = 6371000;
        $dLat = deg2rad($lat2 - $lat1);
        $dLon = deg2rad($lon2 - $lon1);
        $a = sin($dLat / 2) * sin($dLat / 2)
            + cos(deg2rad($lat1)) * cos(deg2rad($lat2))
            * sin($dLon / 2) * sin($dLon / 2);
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        return $earthRadius * $c;
    }

    private function computeCheckInMetrics(
        Carbon $loggedAtUtc,
        string $attendanceTime,
        string $departureTime,
        string $tz,
        bool $allowGracePeriod,
        int $graceMinutes
    ): array
    {
        $scheduledAttendanceTime = $this->normalizeTime($attendanceTime);
        $scheduledDepartureTime = $this->normalizeTime($departureTime);

        if (! $scheduledAttendanceTime || ! $scheduledDepartureTime) {
            return [
                'error_message' => 'لم يتم ضبط وقت الحضور/الانصراف لهذا الموظف',
            ];
        }

        $loggedAtLocal = $loggedAtUtc->copy()->timezone($tz);
        $scheduledAttendanceLocal = $this->combineDateAndTime($loggedAtLocal, $scheduledAttendanceTime, $tz);

        $earliestAllowed = $scheduledAttendanceLocal->copy()->subMinutes(self::EARLY_CHECK_IN_WINDOW_MINUTES);
        if ($loggedAtLocal->lessThan($earliestAllowed)) {
            return [
                'error_message' => 'لا يمكن تسجيل الحضور قبل أكثر من ساعة من وقت الحضور المحدد',
            ];
        }

        $earlyMinutes = null;
        $lateMinutes = null;
        $status = 'present';

        if ($loggedAtLocal->lessThan($scheduledAttendanceLocal)) {
            $earlySeconds = $scheduledAttendanceLocal->diffInSeconds($loggedAtLocal, true);
            $earlyMinutes = (int) ceil($earlySeconds / 60);
            $status = 'present';
        } elseif ($loggedAtLocal->greaterThan($scheduledAttendanceLocal)) {
            $lateSeconds = $loggedAtLocal->diffInSeconds($scheduledAttendanceLocal, true);
            $lateMinutes = (int) ceil($lateSeconds / 60);
            $status = $lateMinutes > self::LATE_ABSENT_THRESHOLD_MINUTES ? 'absent' : 'late';
        }

        if ($earlyMinutes !== null) {
            $earlyMinutes = (int) $earlyMinutes;
            if ($earlyMinutes < 0) {
                $earlyMinutes = null;
            }
        }

        if ($lateMinutes !== null) {
            $lateMinutes = (int) $lateMinutes;
            if ($lateMinutes < 0) {
                $lateMinutes = null;
            }
        }

        $graceMinutes = $allowGracePeriod ? max(0, min(300, (int) $graceMinutes)) : 0;
        if ($lateMinutes !== null && $graceMinutes > 0) {
            $lateAfterGrace = max(0, $lateMinutes - $graceMinutes);
            if ($lateAfterGrace === 0) {
                $lateMinutes = null;
                $status = 'present';
            } else {
                $lateMinutes = $lateAfterGrace;
                $status = $lateMinutes > self::LATE_ABSENT_THRESHOLD_MINUTES ? 'absent' : 'late';
            }
        }

        return [
            'scheduled_attendance_time' => $scheduledAttendanceTime,
            'scheduled_departure_time' => $scheduledDepartureTime,
            'early_minutes' => $earlyMinutes,
            'late_minutes' => $lateMinutes,
            'attendance_status' => $status,
        ];
    }

    private function earlyCheckoutErrorMessage(Carbon $loggedAtUtc, string $departureTime, string $tz): ?string
    {
        $scheduledDepartureTime = $this->normalizeTime($departureTime);
        if (! $scheduledDepartureTime) {
            return 'لم يتم ضبط وقت الانصراف لهذا الموظف';
        }

        $loggedAtLocal = $loggedAtUtc->copy()->timezone($tz);
        $scheduledDepartureLocal = $this->combineDateAndTime($loggedAtLocal, $scheduledDepartureTime, $tz);

        if ($loggedAtLocal->lessThan($scheduledDepartureLocal)) {
            return 'لا يسمح بتسجيل الانصراف قبل وقت الانصراف المحدد';
        }

        return null;
    }

    private function normalizeTime(?string $time): ?string
    {
        $time = $time !== null ? trim($time) : null;
        if ($time === null || $time === '') {
            return null;
        }

        if (preg_match('/^\d{2}:\d{2}$/', $time)) {
            return $time.':00';
        }

        if (preg_match('/^\d{2}:\d{2}:\d{2}$/', $time)) {
            return $time;
        }

        return null;
    }

    private function combineDateAndTime(Carbon $date, string $timeHms, string $tz): Carbon
    {
        return Carbon::createFromFormat(
            'Y-m-d H:i:s',
            $date->copy()->timezone($tz)->toDateString().' '.$timeHms,
            $tz
        );
    }
}
