<?php

namespace App\Modules\HR\Attendance\Services;

use App\Modules\HR\Attendance\Jobs\RunAttendanceExceptionDetectionJob;
use App\Modules\HR\Attendance\Models\AttendanceRecord;
use App\Modules\HR\Attendance\Repositories\AttendanceRecordRepository;
use App\Modules\HR\Attendance\Support\AttendanceSource;
use App\Modules\HR\Employees\Models\Employee;
use App\Modules\Payroll\Services\PayrollProtectionService;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class AttendanceManualEntryService
{
    public function __construct(
        private readonly AttendanceRecordRepository $records,
        private readonly PayrollProtectionService $payrollProtection,
        private readonly AttendanceRecordActivityLogger $activityLog,
    ) {}

    /**
     * @param  array{attendance_date: string, check_in_at: ?string, check_out_at: ?string, status: string, notes: ?string}  $data
     */
    public function upsert(Employee $employee, array $data, int $userId): void
    {
        $record = DB::transaction(function () use ($employee, $data, $userId): AttendanceRecord {
            $this->payrollProtection->ensureDateWritable(
                (int) $employee->tenant_id,
                $data['attendance_date'],
                $userId,
            );
            $saved = $this->records->updateOrCreateDay(
                (int) $employee->tenant_id,
                (int) $employee->id,
                $data['attendance_date'],
                [
                    'check_in_at' => $data['check_in_at'] ?? null,
                    'check_out_at' => $data['check_out_at'] ?? null,
                    'overtime_minutes' => 0,
                    'missing_checkout' => ($data['check_in_at'] ?? null) !== null && ($data['check_out_at'] ?? null) === null,
                    'source' => AttendanceSource::MANUAL,
                    'status' => $data['status'],
                    'notes' => $data['notes'] ?? null,
                    'created_by' => $userId,
                    'updated_by' => $userId,
                ],
            );
            $d = $data['attendance_date'];
            $tenantId = (int) $employee->tenant_id;
            DB::afterCommit(function () use ($tenantId, $d): void {
                RunAttendanceExceptionDetectionJob::dispatch($tenantId, $d, $d);
            });

            return $saved;
        });

        $this->activityLog->log(
            $record,
            'attendance_manual_upsert',
            $userId,
            __('attendance.activity.manual_upsert', ['date' => $data['attendance_date']]),
        );
    }

    public function updateCheckout(AttendanceRecord $record, string $checkOutAt, int $userId): void
    {
        DB::transaction(function () use ($record, $checkOutAt, $userId): void {
            $date = $record->attendance_date?->toDateString();
            if ($date === null) {
                return;
            }
            $this->payrollProtection->ensureDateWritable((int) $record->tenant_id, $date, $userId);
            $checkOut = \Carbon\Carbon::parse($checkOutAt);
            if ($record->check_in_at !== null && $checkOut->lessThan($record->check_in_at)) {
                throw ValidationException::withMessages([
                    'check_out_at' => [__('attendance.validation.check_out_after_check_in')],
                ]);
            }
            $record->forceFill([
                'check_out_at' => $checkOut,
                'missing_checkout' => false,
                'updated_by' => $userId,
            ])->save();
            $tenantId = (int) $record->tenant_id;
            DB::afterCommit(function () use ($tenantId, $date): void {
                RunAttendanceExceptionDetectionJob::dispatch($tenantId, $date, $date);
            });
        });

        $this->activityLog->log(
            $record->fresh() ?? $record,
            'attendance_manual_checkout',
            $userId,
            __('attendance.activity.manual_checkout', [
                'date' => $record->attendance_date?->toDateString() ?? '',
            ]),
        );
    }
}
