<?php

namespace App\Modules\HR\Attendance\Services;

use App\Models\User;
use App\Modules\HR\Attendance\Events\AttendanceCorrectionApproved;
use App\Modules\HR\Attendance\Events\AttendanceCorrectionRejected;
use App\Modules\HR\Attendance\Models\AttendanceCorrectionRequest;
use App\Modules\HR\Attendance\Repositories\AttendanceCorrectionRequestRepository;
use App\Modules\HR\Attendance\Repositories\AttendanceRecordRepository;
use App\Modules\HR\Attendance\Support\AttendanceCorrectionStatus;
use App\Modules\Payroll\Services\PayrollProtectionService;
use Illuminate\Support\Facades\DB;

class AttendanceCorrectionReviewService
{
    public function __construct(
        private readonly AttendanceCorrectionRequestRepository $corrections,
        private readonly AttendanceRecordRepository $records,
        private readonly AttendanceCorrectionPayloadValidator $payloadValidator,
        private readonly AttendanceCorrectionApplierService $applier,
        private readonly PayrollProtectionService $payrollProtection,
    ) {}

    public function approve(
        AttendanceCorrectionRequest $correction,
        User $reviewer,
        string $reviewerNote,
    ): void {
        $tid = (int) $correction->tenant_id;
        $cid = (int) $correction->id;

        $saved = null;
        DB::transaction(function () use ($tid, $cid, $reviewer, $reviewerNote, &$saved): void {
            $locked = $this->corrections->lockForTenant($tid, $cid);
            if ($locked === null) {
                abort(404);
            }
            if ($locked->status !== AttendanceCorrectionStatus::PENDING) {
                abort(422);
            }
            $record = $this->records->findOwnedByEmployee(
                $tid,
                (int) $locked->employee_id,
                (int) $locked->attendance_record_id,
            );
            if ($record === null) {
                abort(404);
            }
            $record->loadMissing('employee');
            $this->payrollProtection->ensureDateWritable(
                $tid,
                $record->attendance_date->toDateString(),
                (int) $reviewer->id,
            );
            $this->payloadValidator->assertForApprove($locked, $record);

            [$patch, $meta] = $this->applier->buildRecordPatchAndMetadata(
                $locked,
                $record,
                (int) $reviewer->id,
            );
            $record->update($patch);
            $saved = $this->corrections->update($locked, [
                'status' => AttendanceCorrectionStatus::APPROVED,
                'reviewed_by' => $reviewer->id,
                'reviewed_at' => now(),
                'reviewer_note' => $reviewerNote,
                'metadata' => $meta,
            ]);
        });

        abort_if($saved === null, 500);
        event(new AttendanceCorrectionApproved($saved, (int) $reviewer->id));
    }

    public function reject(
        AttendanceCorrectionRequest $correction,
        User $reviewer,
        string $reviewerNote,
    ): void {
        $tid = (int) $correction->tenant_id;
        $cid = (int) $correction->id;

        $saved = null;
        DB::transaction(function () use ($tid, $cid, $reviewer, $reviewerNote, &$saved): void {
            $locked = $this->corrections->lockForTenant($tid, $cid);
            if ($locked === null) {
                abort(404);
            }
            if ($locked->status !== AttendanceCorrectionStatus::PENDING) {
                abort(422);
            }
            $saved = $this->corrections->update($locked, [
                'status' => AttendanceCorrectionStatus::REJECTED,
                'reviewed_by' => $reviewer->id,
                'reviewed_at' => now(),
                'reviewer_note' => $reviewerNote,
            ]);
        });

        abort_if($saved === null, 500);
        event(new AttendanceCorrectionRejected($saved, (int) $reviewer->id));
    }
}
