<?php

namespace App\Modules\HR\Leave\Services;

use App\Modules\HR\Attendance\Repositories\AttendanceRecordRepository;
use App\Modules\HR\Attendance\Support\AttendanceRecordStatus;
use App\Modules\HR\Attendance\Support\AttendanceSource;
use App\Modules\HR\Leave\Models\LeaveRequest;
use App\Modules\Payroll\Support\PayrollProtectionAuditAction;
use App\Modules\Payroll\Services\PayrollProtectionService;
use Carbon\CarbonImmutable;

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

    public function applyApprovedLeave(LeaveRequest $leave): void
    {
        $leave->loadMissing('leaveType');
        $tenantId = (int) $leave->tenant_id;
        $employeeId = (int) $leave->employee_id;
        $this->payrollProtection->ensureRangeWritable(
            $tenantId,
            $leave->start_date->toDateString(),
            $leave->end_date->toDateString(),
            $leave->approved_by !== null ? (int) $leave->approved_by : null,
            PayrollProtectionAuditAction::LeaveSync,
        );
        $this->writeLeaveDays($tenantId, $employeeId, $leave);
    }

    /**
     * Removes synced leave rows after approval is withdrawn (e.g. HR rejection).
     * Same payroll locks as apply: cannot mutate attendance inside finalized payroll ranges.
     */
    public function revertSyncedLeaveAttendance(LeaveRequest $leave, int $actorUserId): void
    {
        $leave->loadMissing('leaveType');
        $tenantId = (int) $leave->tenant_id;
        $employeeId = (int) $leave->employee_id;
        $this->payrollProtection->ensureRangeWritable(
            $tenantId,
            $leave->start_date->toDateString(),
            $leave->end_date->toDateString(),
            $actorUserId,
            PayrollProtectionAuditAction::LeaveSync,
        );
        $cur = CarbonImmutable::parse($leave->start_date->toDateString());
        $end = CarbonImmutable::parse($leave->end_date->toDateString());
        $needle = '#'.$leave->id;
        while ($cur->lte($end)) {
            $record = $this->records->findForEmployeeDate($employeeId, $cur->toDateString());
            if ($record !== null
                && $record->source === AttendanceSource::LEAVE
                && $record->status === AttendanceRecordStatus::LEAVE
                && str_contains((string) $record->notes, $needle)) {
                $record->delete();
            }
            $cur = $cur->addDay();
        }
    }

    private function writeLeaveDays(int $tenantId, int $employeeId, LeaveRequest $leave): void
    {
        $cur = CarbonImmutable::parse($leave->start_date->toDateString());
        $end = CarbonImmutable::parse($leave->end_date->toDateString());
        while ($cur->lte($end)) {
            $this->records->updateOrCreateDay($tenantId, $employeeId, $cur->toDateString(), [
                'check_in_at' => null,
                'check_out_at' => null,
                'source' => AttendanceSource::LEAVE,
                'status' => AttendanceRecordStatus::LEAVE,
                'notes' => __('leave.attendance_note', [
                    'id' => $leave->id,
                    'type' => $leave->leaveType?->name ?? '—',
                ]),
                'updated_by' => $leave->approved_by,
            ]);
            $cur = $cur->addDay();
        }
    }
}
