<?php

namespace App\Http\Controllers;

use App\Models\Attendance;
use App\Models\AttendanceSetting;
use App\Models\Employee;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use Symfony\Component\HttpFoundation\StreamedResponse;

class AttendanceController extends Controller
{
    public function index(Request $request)
    {
        if ($this->currentUserHasHiddenAttendanceRole()) {
            abort(403, 'Role ini tidak menggunakan fitur absensi.');
        }

        $today = today();

        $employee = $this->getEmployeeForCurrentUser();

        if ($employee && $this->employeeHasAutoAttendance($employee)) {
            $this->createAutoAttendanceForDate($employee, $today);
        }

        $attendance = null;

        if ($employee) {
            $attendance = Attendance::where('employee_id', $employee->id)
                ->whereDate('attendance_date', $today)
                ->first();
        }

        $setting = AttendanceSetting::where('is_active', true)->first();

        $attendances = Attendance::with('employee')
            ->where(function ($query) {
                $query->whereNull('note')
                    ->orWhere('note', '')
                    ->orWhere('note', 'not like', '%Auto absen%');
            })
            ->latest('attendance_date')
            ->latest('id')
            ->paginate(20);

        return view('pages.attendance.index', [
            'title' => 'Absensi',
            'employee' => $employee,
            'attendance' => $attendance,
            'setting' => $setting,
            'attendances' => $attendances,
        ]);
    }

    public function recap(Request $request)
    {
        if ($this->currentUserHasHiddenAttendanceRole()) {
            abort(403, 'Role ini tidak menggunakan fitur absensi.');
        }

        $date = $request->get('date', now()->format('Y-m-d'));
        $employeeId = $request->get('employee_id');

        try {
            $selectedDate = Carbon::parse($date)->startOfDay();
            $date = $selectedDate->format('Y-m-d');
        } catch (\Throwable $e) {
            $selectedDate = today();
            $date = today()->format('Y-m-d');
        }

        $startDate = $selectedDate->copy()->startOfMonth();
        $endDate = $selectedDate->copy()->endOfDay();

        $this->createAutoAttendanceForPeriod($startDate, $selectedDate);

        $employees = Employee::query()
            ->when(Schema::hasColumn('employees', 'status'), function ($query) {
                $query->where('status', 'active');
            })
            ->orderBy('name')
            ->get();

        $query = Attendance::with('employee')
            ->whereBetween('attendance_date', [
                $startDate->toDateString(),
                $selectedDate->toDateString(),
            ]);

        if ($employeeId) {
            $query->where('employee_id', $employeeId);
        }

        $attendances = $query
            ->orderBy('attendance_date')
            ->orderBy('employee_id')
            ->get();

        $summary = [
            'total_present' => $attendances->whereNotNull('check_in_at')->count(),
            'total_late' => $attendances->where('late_minutes', '>', 0)->count(),
            'total_early_leave' => $attendances->where('early_leave_minutes', '>', 0)->count(),
            'total_complete' => $attendances->filter(fn ($item) => $item->check_in_at && $item->check_out_at)->count(),
            'total_incomplete' => $attendances->filter(fn ($item) => $item->check_in_at && !$item->check_out_at)->count(),
            'late_minutes' => $attendances->sum('late_minutes'),
            'early_leave_minutes' => $attendances->sum('early_leave_minutes'),
        ];

        $performanceInsight = $this->buildPerformanceInsight($attendances, $startDate, $selectedDate);

        return view('pages.attendance.recap', [
            'title' => 'Rekap Absensi Bulan Berjalan',
            'date' => $date,
            'selectedDate' => $selectedDate,
            'startDate' => $startDate,
            'endDate' => $endDate,
            'periodLabel' => $startDate->format('d/m/Y') . ' - ' . $selectedDate->format('d/m/Y'),
            'employees' => $employees,
            'employeeId' => $employeeId,
            'attendances' => $attendances,
            'summary' => $summary,
            'performanceInsight' => $performanceInsight,
        ]);
    }

    public function export(Request $request): StreamedResponse
    {
        if ($this->currentUserHasHiddenAttendanceRole()) {
            abort(403, 'Role ini tidak menggunakan fitur absensi.');
        }

        $date = $request->get('date', now()->format('Y-m-d'));
        $employeeId = $request->get('employee_id');

        try {
            $selectedDate = Carbon::parse($date)->startOfDay();
            $date = $selectedDate->format('Y-m-d');
        } catch (\Throwable $e) {
            $selectedDate = today();
            $date = today()->format('Y-m-d');
        }

        $startDate = $selectedDate->copy()->startOfMonth();

        $this->createAutoAttendanceForPeriod($startDate, $selectedDate);

        $query = Attendance::with('employee')
            ->whereBetween('attendance_date', [
                $startDate->toDateString(),
                $selectedDate->toDateString(),
            ]);

        if ($employeeId) {
            $query->where('employee_id', $employeeId);
        }

        $filename = 'rekap-absensi-' . $startDate->format('Y-m-d') . '-sd-' . $date . '.csv';

        return response()->streamDownload(function () use ($query) {
            $handle = fopen('php://output', 'w');

            fputcsv($handle, [
                'Tanggal',
                'ID Karyawan',
                'Nama Karyawan',
                'Jam Masuk',
                'Jam Pulang',
                'Jarak Masuk Meter',
                'Jarak Pulang Meter',
                'Telat Menit',
                'Pulang Cepat Menit',
                'Status',
                'Catatan',
            ]);

            $query->orderBy('attendance_date')
                ->orderBy('employee_id')
                ->chunk(200, function ($attendances) use ($handle) {
                    foreach ($attendances as $attendance) {
                        $note = $attendance->note ?? '';

                        $isAutoAttendance = false;

                        if ($attendance->employee && Schema::hasColumn('employees', 'is_auto_attendance')) {
                            $isAutoAttendance = (bool) ($attendance->employee->is_auto_attendance ?? false);
                        }

                        $isAutoNote = str_contains(strtolower((string) $note), 'auto absen');

                        $exportNote = ($isAutoAttendance || $isAutoNote) ? '' : $note;

                        fputcsv($handle, [
                            optional($attendance->attendance_date)->format('d/m/Y'),
                            $attendance->employee->employee_id ?? '',
                            $attendance->employee->name ?? '',
                            optional($attendance->check_in_at)->format('H:i:s'),
                            optional($attendance->check_out_at)->format('H:i:s'),
                            $attendance->check_in_distance_meters ?? 0,
                            $attendance->check_out_distance_meters ?? 0,
                            $attendance->late_minutes ?? 0,
                            $attendance->early_leave_minutes ?? 0,
                            $attendance->status ?? 'present',
                            $exportNote,
                        ]);
                    }
                });

            fclose($handle);
        }, $filename, [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    public function checkIn(Request $request)
    {
        if ($this->currentUserHasHiddenAttendanceRole()) {
            return back()->with('error', 'Role ini tidak menggunakan fitur absensi.');
        }

        $request->validate([
            'latitude' => ['required', 'numeric'],
            'longitude' => ['required', 'numeric'],
        ]);

        $employee = $this->getEmployeeForCurrentUser();

        if (!$employee) {
            return back()->with('error', 'Akun ini tidak terhubung dengan data karyawan, sehingga tidak bisa melakukan absensi.');
        }

        if ($this->employeeHasAutoAttendance($employee)) {
            $this->createAutoAttendanceForDate($employee, today());

            return back()->with('success', 'Absensi hari ini berhasil tercatat.');
        }

        $setting = AttendanceSetting::where('is_active', true)->first();

        if (!$setting) {
            return back()->with('error', 'Pengaturan lokasi absensi belum aktif.');
        }

        $distance = $this->calculateDistanceMeters(
            (float) $request->latitude,
            (float) $request->longitude,
            (float) $setting->latitude,
            (float) $setting->longitude
        );

        if ($distance > $setting->radius_meters) {
            return back()->with('error', 'Absensi ditolak. Lokasi kamu di luar radius yang ditentukan. Jarak: ' . round($distance) . ' meter.');
        }

        $attendance = Attendance::firstOrCreate(
            [
                'employee_id' => $employee->id,
                'attendance_date' => today(),
            ],
            [
                'status' => 'present',
            ]
        );

        if ($attendance->check_in_at) {
            return back()->with('error', 'Kamu sudah absen masuk hari ini.');
        }

        $lateMinutes = $this->calculateLateMinutes(now(), $setting);

        $attendance->update([
            'check_in_at' => now(),
            'check_in_latitude' => $request->latitude,
            'check_in_longitude' => $request->longitude,
            'check_in_distance_meters' => round($distance),
            'late_minutes' => $lateMinutes,
            'status' => $lateMinutes > 0 ? 'late' : 'present',
        ]);

        if ($lateMinutes > 0) {
            return back()->with('success', 'Absen masuk berhasil. Kamu telat ' . $lateMinutes . ' menit.');
        }

        return back()->with('success', 'Absen masuk berhasil.');
    }

    public function checkOut(Request $request)
    {
        if ($this->currentUserHasHiddenAttendanceRole()) {
            return back()->with('error', 'Role ini tidak menggunakan fitur absensi.');
        }

        $request->validate([
            'latitude' => ['required', 'numeric'],
            'longitude' => ['required', 'numeric'],
        ]);

        $employee = $this->getEmployeeForCurrentUser();

        if (!$employee) {
            return back()->with('error', 'Akun ini tidak terhubung dengan data karyawan, sehingga tidak bisa melakukan absensi.');
        }

        if ($this->employeeHasAutoAttendance($employee)) {
            $this->createAutoAttendanceForDate($employee, today());

            return back()->with('success', 'Absensi hari ini berhasil tercatat.');
        }

        $setting = AttendanceSetting::where('is_active', true)->first();

        if (!$setting) {
            return back()->with('error', 'Pengaturan lokasi absensi belum aktif.');
        }

        $attendance = Attendance::where('employee_id', $employee->id)
            ->whereDate('attendance_date', today())
            ->first();

        if (!$attendance || !$attendance->check_in_at) {
            return back()->with('error', 'Kamu belum absen masuk hari ini.');
        }

        if ($attendance->check_out_at) {
            return back()->with('error', 'Kamu sudah absen pulang hari ini.');
        }

        $distance = $this->calculateDistanceMeters(
            (float) $request->latitude,
            (float) $request->longitude,
            (float) $setting->latitude,
            (float) $setting->longitude
        );

        if ($distance > $setting->radius_meters) {
            return back()->with('error', 'Absen pulang ditolak. Lokasi kamu di luar radius yang ditentukan. Jarak: ' . round($distance) . ' meter.');
        }

        if ($setting->work_end_time) {
            $scheduledEnd = Carbon::parse(now()->toDateString() . ' ' . $setting->work_end_time);
            $tolerance = (int) ($setting->early_leave_tolerance_minutes ?? 0);
            $allowedCheckoutTime = $scheduledEnd->copy()->subMinutes($tolerance);

            if (now()->lt($allowedCheckoutTime)) {
                return back()->with(
                    'error',
                    'Belum waktunya absen pulang. Absen pulang baru bisa dilakukan mulai pukul ' . $allowedCheckoutTime->format('H:i') . '.'
                );
            }
        }

        $earlyLeaveMinutes = $this->calculateEarlyLeaveMinutes(now(), $setting);

        $attendance->update([
            'check_out_at' => now(),
            'check_out_latitude' => $request->latitude,
            'check_out_longitude' => $request->longitude,
            'check_out_distance_meters' => round($distance),
            'early_leave_minutes' => $earlyLeaveMinutes,
        ]);

        if ($earlyLeaveMinutes > 0) {
            return back()->with('success', 'Absen pulang berhasil. Pulang cepat ' . $earlyLeaveMinutes . ' menit.');
        }

        return back()->with('success', 'Absen pulang berhasil.');
    }



    private function currentUserHasHiddenAttendanceRole(): bool
    {
        $user = Auth::user();

        if (!$user) {
            return false;
        }

        $hiddenRoles = ['developer', 'owner'];
        $userRoles = [];

        if (method_exists($user, 'hasAnyRole')) {
            try {
                if ($user->hasAnyRole($hiddenRoles)) {
                    return true;
                }
            } catch (\Throwable $e) {
                // Fallback ke pengecekan manual di bawah.
            }
        }

        if (method_exists($user, 'getRoleNames')) {
            try {
                $userRoles = array_merge(
                    $userRoles,
                    $user->getRoleNames()->map(fn ($role) => strtolower((string) $role))->toArray()
                );
            } catch (\Throwable $e) {
                // Fallback ke pengecekan kolom manual di bawah.
            }
        }

        foreach (['role', 'role_name'] as $roleColumn) {
            if (!empty($user->{$roleColumn}) && is_string($user->{$roleColumn})) {
                $userRoles[] = strtolower((string) $user->{$roleColumn});
            }
        }

        if (!empty($user->role) && is_object($user->role) && !empty($user->role->name)) {
            $userRoles[] = strtolower((string) $user->role->name);
        }

        return count(array_intersect($hiddenRoles, array_unique($userRoles))) > 0;
    }

    private function buildPerformanceInsight($attendances, ?Carbon $startDate = null, ?Carbon $selectedDate = null): array
    {
        $manualAttendances = $attendances
            ->reject(fn ($attendance) => $this->isAutoAttendanceRecord($attendance))
            ->values();

        $employeePerformances = $manualAttendances
            ->groupBy('employee_id')
            ->map(function ($items) {
                $first = $items->first();
                $employee = $first?->employee;

                $totalDays = $items->count();
                $completeDays = $items->filter(fn ($attendance) => $attendance->check_in_at && $attendance->check_out_at)->count();
                $lateCount = $items->filter(fn ($attendance) => (int) ($attendance->late_minutes ?? 0) > 0)->count();
                $earlyLeaveCount = $items->filter(fn ($attendance) => (int) ($attendance->early_leave_minutes ?? 0) > 0)->count();
                $incompleteCount = $items->filter(fn ($attendance) => $attendance->check_in_at && !$attendance->check_out_at)->count();
                $missingCheckInCount = $items->filter(fn ($attendance) => !$attendance->check_in_at)->count();
                $totalLateMinutes = $items->sum(fn ($attendance) => (int) ($attendance->late_minutes ?? 0));
                $totalEarlyLeaveMinutes = $items->sum(fn ($attendance) => (int) ($attendance->early_leave_minutes ?? 0));

                $issueDays = $items->filter(function ($attendance) {
                    return !$attendance->check_in_at
                        || !$attendance->check_out_at
                        || (int) ($attendance->late_minutes ?? 0) > 0
                        || (int) ($attendance->early_leave_minutes ?? 0) > 0;
                })->count();

                $disciplinedDays = $items->filter(function ($attendance) {
                    return $attendance->check_in_at
                        && $attendance->check_out_at
                        && (int) ($attendance->late_minutes ?? 0) === 0
                        && (int) ($attendance->early_leave_minutes ?? 0) === 0;
                })->count();

                $score = $totalLateMinutes
                    + $totalEarlyLeaveMinutes
                    + ($incompleteCount * 60)
                    + ($missingCheckInCount * 120);

                return [
                    'employee_id' => $employee?->id,
                    'employee_code' => $employee->employee_id ?? '-',
                    'employee_name' => $employee->name ?? '-',
                    'total_days' => $totalDays,
                    'complete_days' => $completeDays,
                    'disciplined_days' => $disciplinedDays,
                    'issue_days' => $issueDays,
                    'late_count' => $lateCount,
                    'early_leave_count' => $earlyLeaveCount,
                    'incomplete_count' => $incompleteCount,
                    'missing_check_in_count' => $missingCheckInCount,
                    'total_late_minutes' => $totalLateMinutes,
                    'total_early_leave_minutes' => $totalEarlyLeaveMinutes,
                    'score' => $score,
                ];
            })
            ->values();

        $topDisciplined = $employeePerformances
            ->filter(fn ($item) => (int) $item['score'] === 0 && (int) $item['complete_days'] > 0)
            ->sortBy([
                ['disciplined_days', 'desc'],
                ['complete_days', 'desc'],
                ['employee_name', 'asc'],
            ])
            ->take(5)
            ->values();

        $needsAttention = $employeePerformances
            ->filter(fn ($item) => (int) $item['score'] > 0)
            ->sortBy([
                ['score', 'desc'],
                ['issue_days', 'desc'],
                ['employee_name', 'asc'],
            ])
            ->take(5)
            ->values();

        $totalManualRecords = $manualAttendances->count();
        $totalManualEmployees = $employeePerformances->count();
        $totalDisciplinedEmployees = $employeePerformances->filter(fn ($item) => (int) $item['score'] === 0 && (int) $item['complete_days'] > 0)->count();
        $totalAttentionEmployees = $employeePerformances->filter(fn ($item) => (int) $item['score'] > 0)->count();
        $totalIncomplete = $employeePerformances->sum('incomplete_count');
        $totalMissingCheckIn = $employeePerformances->sum('missing_check_in_count');
        $totalLateMinutes = $employeePerformances->sum('total_late_minutes');
        $totalEarlyLeaveMinutes = $employeePerformances->sum('total_early_leave_minutes');
        $autoExcluded = $attendances->count() - $totalManualRecords;
        $periodText = $startDate && $selectedDate
            ? $startDate->format('d/m/Y') . ' - ' . $selectedDate->format('d/m/Y')
            : 'periode terpilih';

        if ($totalManualRecords === 0) {
            $headline = 'Belum ada absensi manual yang bisa dianalisis pada ' . $periodText . '.';
        } elseif ($totalAttentionEmployees === 0) {
            $headline = 'Semua karyawan manual yang tercatat pada ' . $periodText . ' disiplin dan absensinya lengkap.';
        } else {
            $headline = $totalAttentionEmployees . ' dari ' . $totalManualEmployees . ' karyawan manual perlu perhatian pada ' . $periodText . ' berdasarkan akumulasi telat, pulang cepat, atau absensi belum lengkap.';
        }

        return [
            'headline' => $headline,
            'period_label' => $periodText,
            'total_manual' => $totalManualEmployees,
            'total_manual_records' => $totalManualRecords,
            'total_disciplined' => $totalDisciplinedEmployees,
            'total_attention' => $totalAttentionEmployees,
            'total_incomplete' => $totalIncomplete,
            'total_missing_check_in' => $totalMissingCheckIn,
            'total_late_minutes' => $totalLateMinutes,
            'total_early_leave_minutes' => $totalEarlyLeaveMinutes,
            'auto_excluded' => $autoExcluded,
            'top_disciplined' => $topDisciplined,
            'needs_attention' => $needsAttention,
        ];
    }

    private function isAutoAttendanceRecord(Attendance $attendance): bool
    {
        $employee = $attendance->employee;

        if ($employee && Schema::hasColumn('employees', 'is_auto_attendance') && (bool) ($employee->is_auto_attendance ?? false)) {
            return true;
        }

        $note = strtolower((string) ($attendance->note ?? ''));

        return str_contains($note, 'auto absen');
    }

    private function employeeHasAutoAttendance(Employee $employee): bool
    {
        if (!Schema::hasColumn('employees', 'is_auto_attendance')) {
            return false;
        }

        return (bool) ($employee->is_auto_attendance ?? false);
    }

    private function createAutoAttendanceForAllAutoEmployeesByDate(Carbon $date): void
    {
        if (!Schema::hasColumn('employees', 'is_auto_attendance')) {
            return;
        }

        if ($date->greaterThan(today())) {
            return;
        }

        if ($date->isSunday()) {
            return;
        }

        $employees = Employee::query()
            ->where('is_auto_attendance', true)
            ->when(Schema::hasColumn('employees', 'status'), function ($query) {
                $query->where('status', 'active');
            })
            ->get();

        foreach ($employees as $employee) {
            $this->createAutoAttendanceForDate($employee, $date);
        }
    }

    private function createAutoAttendanceForPeriod(Carbon $startDate, Carbon $endDate): void
    {
        if (!Schema::hasColumn('employees', 'is_auto_attendance')) {
            return;
        }

        $today = today();

        if ($startDate->greaterThan($today)) {
            return;
        }

        $generateUntil = $endDate->greaterThan($today)
            ? $today->copy()
            : $endDate->copy();

        $employees = Employee::query()
            ->where('is_auto_attendance', true)
            ->when(Schema::hasColumn('employees', 'status'), function ($query) {
                $query->where('status', 'active');
            })
            ->get();

        foreach ($employees as $employee) {
            $date = $startDate->copy();

            while ($date->lessThanOrEqualTo($generateUntil)) {
                if (!$date->isSunday()) {
                    $this->createAutoAttendanceForDate($employee, $date);
                }

                $date->addDay();
            }
        }
    }

    private function createAutoAttendanceForDate(Employee $employee, Carbon $date): Attendance
    {
        $setting = AttendanceSetting::where('is_active', true)->first();

        $workStart = $setting?->work_start_time ? substr($setting->work_start_time, 0, 5) : '08:00';
        $workEnd = $setting?->work_end_time ? substr($setting->work_end_time, 0, 5) : '17:00';

        $checkInAt = Carbon::parse($date->toDateString() . ' ' . $workStart);
        $checkOutAt = Carbon::parse($date->toDateString() . ' ' . $workEnd);

        $note = 'Auto absen / jam flexible';

        if (Schema::hasColumn('employees', 'auto_attendance_note') && !empty($employee->auto_attendance_note)) {
            $note = $employee->auto_attendance_note;
        }

        return Attendance::updateOrCreate(
            [
                'employee_id' => $employee->id,
                'attendance_date' => $date->toDateString(),
            ],
            [
                'check_in_at' => $checkInAt,
                'check_out_at' => $checkOutAt,
                'check_in_latitude' => $setting?->latitude,
                'check_in_longitude' => $setting?->longitude,
                'check_out_latitude' => $setting?->latitude,
                'check_out_longitude' => $setting?->longitude,
                'check_in_distance_meters' => 0,
                'check_out_distance_meters' => 0,
                'late_minutes' => 0,
                'early_leave_minutes' => 0,
                'status' => 'present',
                'note' => $note,
            ]
        );
    }

    private function calculateLateMinutes(Carbon $checkInTime, AttendanceSetting $setting): int
    {
        if (!$setting->work_start_time) {
            return 0;
        }

        $scheduledStart = Carbon::parse($checkInTime->toDateString() . ' ' . $setting->work_start_time);
        $tolerance = (int) ($setting->late_tolerance_minutes ?? 0);
        $allowedStart = $scheduledStart->copy()->addMinutes($tolerance);

        if ($checkInTime->lessThanOrEqualTo($allowedStart)) {
            return 0;
        }

        return $allowedStart->diffInMinutes($checkInTime);
    }

    private function calculateEarlyLeaveMinutes(Carbon $checkOutTime, AttendanceSetting $setting): int
    {
        if (!$setting->work_end_time) {
            return 0;
        }

        $scheduledEnd = Carbon::parse($checkOutTime->toDateString() . ' ' . $setting->work_end_time);
        $tolerance = (int) ($setting->early_leave_tolerance_minutes ?? 0);
        $allowedEnd = $scheduledEnd->copy()->subMinutes($tolerance);

        if ($checkOutTime->greaterThanOrEqualTo($allowedEnd)) {
            return 0;
        }

        return $checkOutTime->diffInMinutes($allowedEnd);
    }

    private function getEmployeeForCurrentUser(): ?Employee
    {
        $user = Auth::user();

        if (!$user) {
            return null;
        }

        if (!Schema::hasTable('employees')) {
            return null;
        }

        if (Schema::hasColumn('employees', 'user_id')) {
            $employee = Employee::where('user_id', $user->id)->first();

            if ($employee) {
                return $employee;
            }
        }

        if (Schema::hasColumn('employees', 'email') && !empty($user->email)) {
            $employee = Employee::where('email', $user->email)->first();

            if ($employee) {
                return $employee;
            }
        }

        if (Schema::hasColumn('employees', 'name') && !empty($user->name)) {
            $employee = Employee::where('name', $user->name)->first();

            if ($employee) {
                return $employee;
            }
        }

        return null;
    }

    private function calculateDistanceMeters(float $lat1, float $lon1, float $lat2, float $lon2): float
    {
        $earthRadius = 6371000;

        $lat1Rad = deg2rad($lat1);
        $lat2Rad = deg2rad($lat2);

        $deltaLat = deg2rad($lat2 - $lat1);
        $deltaLon = deg2rad($lon2 - $lon1);

        $a = sin($deltaLat / 2) ** 2
            + cos($lat1Rad) * cos($lat2Rad) * sin($deltaLon / 2) ** 2;

        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        return $earthRadius * $c;
    }
}