<?php

namespace App\Services\Admin\HR;

use App\Models\Attendance;
use App\Models\LeaveRequest;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

class AttendanceService
{
    use RegistersMedia;

    public function __construct(
        private readonly S3PresignedService $s3Service,
    ) {}

    public function getAll(): Collection
    {
        return Attendance::with(['employee.user.userProfile', 'media'])
            ->latest('attendance_date')
            ->get()
            ->map(fn (Attendance $attendance) => $this->formatAttendance($attendance));
    }

    public function getByMonth(int $year, int $month): Collection
    {
        return Attendance::with(['employee.user.userProfile', 'media'])
            ->whereYear('attendance_date', $year)
            ->whereMonth('attendance_date', $month)
            ->get()
            ->map(fn (Attendance $attendance) => $this->formatAttendance($attendance));
    }

    public function getByDate(string $date): ?array
    {
        $attendance = Attendance::with(['employee.user.userProfile', 'media'])
            ->where('attendance_date', $date)
            ->where('employee_id', auth()->user()->employee?->id)
            ->first();

        return $attendance ? $this->formatAttendance($attendance) : null;
    }

    public function getToday(): ?array
    {
        return $this->getByDate(now()->toDateString());
    }

    public function getMonthStats(int $year, int $month): array
    {
        $startOfMonth = Carbon::createFromDate($year, $month, 1)->startOfMonth();
        $endOfMonth = $startOfMonth->copy()->endOfMonth();

        $workingDays = 0;
        $current = $startOfMonth->copy();
        while ($current->lte($endOfMonth)) {
            if (! in_array($current->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY])) {
                $workingDays++;
            }
            $current->addDay();
        }

        $attendanceCount = Attendance::whereYear('attendance_date', $year)
            ->whereMonth('attendance_date', $month)
            ->count();

        $leaveDays = LeaveRequest::approved()
            ->where('start_date', '<=', $endOfMonth)
            ->where('end_date', '>=', $startOfMonth)
            ->get()
            ->reduce(function ($carry, $leave) use ($startOfMonth, $endOfMonth) {
                $leaveStart = max($leave->start_date->timestamp, $startOfMonth->timestamp);
                $leaveEnd = min($leave->end_date->timestamp, $endOfMonth->timestamp);
                $days = Carbon::createFromTimestamp($leaveStart)->diffInDays(Carbon::createFromTimestamp($leaveEnd)) + 1;

                return $carry + max(0, $days);
            }, 0);

        return [
            'working_days' => $workingDays,
            'present' => $attendanceCount,
            'absent' => max(0, $workingDays - $attendanceCount - $leaveDays),
            'leave' => $leaveDays,
        ];
    }

    public function checkIn(array $data): Attendance
    {
        $employee = auth()->user()->employee;

        if (! $employee) {
            throw new \Exception('Anda tidak terdaftar sebagai karyawan.');
        }

        $today = now()->toDateString();

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

        if ($existing) {
            throw new \Exception('Anda sudah melakukan presensi hari ini.');
        }

        $attendance = Attendance::create([
            'employee_id' => $employee->id,
            'attendance_date' => $today,
            'check_in_at' => now(),
            'check_in_latitude' => $data['latitude'],
            'check_in_longitude' => $data['longitude'],
        ]);

        if (! empty($data['photo'])) {
            $this->registerMediaFromBase64($attendance, $data['photo'], 'check-in', 'attendances');
        }

        NotificationService::notify(
            roles: ['Owner', 'Developer', 'Direktur'],
            title: 'Presensi Masuk',
            body: 'Presensi masuk oleh '.auth()->user()->full_name.'.',
            url: route('admin.hr.attendances.index'),
        );

        return $attendance;
    }

    public function checkOut(Attendance $attendance, array $data): Attendance
    {
        $attendance->update([
            'check_out_at' => now(),
            'check_out_latitude' => $data['latitude'],
            'check_out_longitude' => $data['longitude'],
        ]);

        if (! empty($data['photo'])) {
            $this->registerMediaFromBase64($attendance, $data['photo'], 'check-out', 'attendances');
        }

        NotificationService::notify(
            roles: ['Owner', 'Developer', 'Direktur'],
            title: 'Presensi Pulang',
            body: 'Presensi pulang oleh '.auth()->user()->full_name.'.',
            url: route('admin.hr.attendances.index'),
        );

        return $attendance;
    }

    private function formatAttendance(Attendance $attendance): array
    {
        $toArray = $attendance->toArray();

        if (is_null($toArray['work_duration_minutes']) && $attendance->check_in_at) {
            $checkIn = Carbon::parse($attendance->check_in_at);
            $end = $checkIn->toDateString() === now()->toDateString()
                ? now()
                : $checkIn->copy()->endOfDay();
            $toArray['work_duration_minutes'] = $checkIn->diffInMinutes($end);
        }

        $checkInMedia = $attendance->getFirstMedia('check-in');
        $checkOutMedia = $attendance->getFirstMedia('check-out');

        $toArray['check_in_photo'] = $checkInMedia
            ? Cache::remember("attendance_check_in_{$checkInMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkInMedia->file_name))
            : null;

        $toArray['check_out_photo'] = $checkOutMedia
            ? Cache::remember("attendance_check_out_{$checkOutMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkOutMedia->file_name))
            : null;

        return $toArray;
    }
}
