<?php

namespace App\Modules\HR\Attendance\Services;

use App\Modules\Admin\Tenancy\Models\Tenant;
use App\Modules\HR\Attendance\Models\AttendanceRecord;
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\Employees\Models\Employee;
use App\Modules\HR\Employees\Repositories\EmployeeRepository;
use App\Modules\Payroll\Exceptions\PayrollPeriodLockedException;
use App\Modules\Payroll\Services\PayrollProtectionService;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;

class AttendanceAbsenceGenerationService
{
    public function __construct(
        private readonly EmployeeRepository $employees,
        private readonly AttendanceRecordRepository $records,
        private readonly AttendanceWorkingDayResolver $workingDays,
        private readonly PayrollProtectionService $payrollProtection,
        private readonly AttendanceRecordActivityLogger $activityLog,
    ) {}

    public function generateForTenantDate(Tenant $tenant, string $dateYmd, ?int $actorUserId = null): int
    {
        $day = CarbonImmutable::parse($dateYmd)->startOfDay();
        $created = 0;
        foreach ($this->employees->cursorActiveForTenant((int) $tenant->id) as $employee) {
            if ($this->markAbsentIfNeeded($tenant, $employee, $day, $actorUserId)) {
                $created++;
            }
        }

        return $created;
    }

    private function markAbsentIfNeeded(
        Tenant $tenant,
        Employee $employee,
        CarbonImmutable $day,
        ?int $actorUserId,
    ): bool {
        if (! $this->workingDays->isWorkingDay($employee, $day)) {
            return false;
        }
        $date = $day->toDateString();
        if ($this->records->findForEmployeeDateInTenant((int) $tenant->id, (int) $employee->id, $date)) {
            return false;
        }
        try {
            $this->payrollProtection->ensureDateWritable((int) $tenant->id, $date, $actorUserId);
        } catch (PayrollPeriodLockedException) {
            return false;
        }
        $record = $this->createAbsentRow($tenant, $employee, $date, $actorUserId);
        if ($record === null) {
            return false;
        }
        $this->activityLog->log(
            $record,
            'attendance_auto_absent',
            $actorUserId ?? 0,
            __('attendance.activity.auto_absent', [
                'employee' => $employee->full_name,
                'date' => $date,
            ]),
        );

        return true;
    }

    private function createAbsentRow(
        Tenant $tenant,
        Employee $employee,
        string $date,
        ?int $actorUserId,
    ): ?AttendanceRecord {
        return DB::transaction(function () use ($tenant, $employee, $date, $actorUserId): ?AttendanceRecord {
            if ($this->records->lockForEmployeeDate((int) $tenant->id, (int) $employee->id, $date) !== null) {
                return null;
            }

            return $this->records->updateOrCreateDay(
                (int) $tenant->id,
                (int) $employee->id,
                $date,
                [
                    'check_in_at' => null,
                    'check_out_at' => null,
                    'overtime_minutes' => 0,
                    'missing_checkout' => false,
                    'source' => AttendanceSource::SYSTEM,
                    'status' => AttendanceRecordStatus::ABSENT,
                    'notes' => __('attendance.notes.auto_absent'),
                    'created_by' => $actorUserId,
                    'updated_by' => $actorUserId,
                ],
            );
        });
    }
}
