<?php

namespace App\Modules\HR\Attendance\Services\ExceptionDetection;

use App\Modules\HR\Attendance\Contracts\AttendanceExceptionDetectionRule;
use App\Modules\HR\Attendance\Models\AttendanceRecord;
use App\Modules\HR\Attendance\Repositories\AttendanceExceptionRepository;
use App\Modules\HR\Attendance\Support\AttendanceExceptionDedupeKey;
use App\Modules\HR\Attendance\Support\AttendanceExceptionSeverity;
use App\Modules\HR\Attendance\Support\AttendanceExceptionType;
use App\Modules\HR\Employees\Models\Employee;
use App\Modules\HR\Shifts\Services\ShiftResolutionService;
use App\Modules\HR\Shifts\Support\ShiftScheduleEndpoints;
use Carbon\CarbonImmutable;

class MissingCheckoutExceptionRule implements AttendanceExceptionDetectionRule
{
    public function __construct(
        private readonly AttendanceExceptionRepository $exceptions,
        private readonly ShiftResolutionService $shifts,
    ) {}

    public function run(int $tenantId, CarbonImmutable $from, CarbonImmutable $to): int
    {
        $hours = (float) config('attendance.missing_checkout_hours_after_shift_end', 2);
        $fallbackHours = (float) config('attendance.missing_checkout_hours_after_check_in', 16);
        $created = 0;

        AttendanceRecord::query()
            ->where('tenant_id', $tenantId)
            ->whereBetween('attendance_date', [$from->toDateString(), $to->toDateString()])
            ->whereNotNull('check_in_at')
            ->whereNull('check_out_at')
            ->with('employee')
            ->chunkById(500, function ($chunk) use ($tenantId, $hours, $fallbackHours, &$created): void {
                foreach ($chunk as $record) {
                    if ($this->shouldFlag($record, $hours, $fallbackHours) === false) {
                        continue;
                    }
                    $dayKey = $record->attendance_date->toDateString();
                    $key = AttendanceExceptionDedupeKey::forRecordType(
                        $tenantId,
                        (int) $record->employee_id,
                        (int) $record->id,
                        AttendanceExceptionType::MISSING_CHECKOUT,
                        $dayKey,
                    );
                    $created += (int) $this->exceptions->tryInsert(
                        $tenantId,
                        (int) $record->employee_id,
                        (int) $record->id,
                        AttendanceExceptionType::MISSING_CHECKOUT,
                        AttendanceExceptionSeverity::CRITICAL,
                        $key,
                        ['attendance_date' => $dayKey],
                    );
                }
            });

        return $created;
    }

    private function shouldFlag(AttendanceRecord $record, float $hoursAfterEnd, float $fallbackHours): bool
    {
        if ($record->missing_checkout === true) {
            return true;
        }
        $emp = $record->employee;
        if (! $emp instanceof Employee || $record->check_in_at === null) {
            return false;
        }
        $day = CarbonImmutable::parse($record->attendance_date->toDateString())->startOfDay();
        $ctx = $this->shifts->resolveContextForAttendanceDate(
            $emp,
            $record->attendance_date->toDateString(),
            CarbonImmutable::parse($record->check_in_at->toIso8601String()),
        );
        if ($ctx !== null) {
            $end = ShiftScheduleEndpoints::shiftEndOnDate($ctx->shift, $day);

            return now()->greaterThan($end->addMinutes((int) round($hoursAfterEnd * 60)));
        }

        return now()->greaterThan($record->check_in_at->copy()->addHours((int) round($fallbackHours)));
    }
}
