<?php

namespace App\Services\Biometric;

use App\Models\Company;
use App\Models\WorkDuration;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class StaleOpenSessionCleaner
{
    /**
     * Close open WorkDuration rows whose session start is older than max age
     * (or attendance date before a cutoff). Does not invent clock-out on attendance —
     * only closes the orphan session so biometric Both-mode can work on the current day.
     *
     * @return array{scanned: int, closed: int, dry_run: bool, max_age_hours: int}
     */
    public function closeStale(
        ?int $companyId = null,
        ?int $maxAgeHours = null,
        ?Carbon $beforeDate = null,
        bool $dryRun = false
    ): array {
        $maxAgeHours = max(1, $maxAgeHours ?? (int) config('zkteco.open_session_max_age_hours', 20));
        $cutoff = now()->subHours($maxAgeHours);
        $beforeDate = $beforeDate?->toDateString();

        $query = WorkDuration::withoutGlobalScopes()
            ->whereNull('end_time')
            ->with(['attendance' => function ($q) {
                $q->withoutGlobalScopes();
            }])
            ->orderBy('id');

        if ($companyId !== null) {
            $query->where('company_id', $companyId);
        }

        $scanned = 0;
        $closed = 0;

        $query->chunkById(200, function ($sessions) use (
            &$scanned,
            &$closed,
            $cutoff,
            $beforeDate,
            $dryRun,
            $maxAgeHours
        ) {
            foreach ($sessions as $session) {
                $scanned++;
                /** @var WorkDuration $session */
                $attendance = $session->attendance;
                if (! $attendance) {
                    continue;
                }

                $company = Company::withoutGlobalScopes()->find($session->company_id);
                $tz = $company?->timezone ?: config('app.timezone', 'Asia/Kolkata');

                $attendanceDate = $attendance->date instanceof Carbon
                    ? $attendance->date->toDateString()
                    : Carbon::parse($attendance->date)->toDateString();

                if ($beforeDate !== null && $attendanceDate >= $beforeDate) {
                    continue;
                }

                $sessionStart = Carbon::parse(
                    $attendanceDate.' '.substr((string) $session->start_time, 0, 8),
                    $tz
                );

                $isStaleByAge = $sessionStart->lt($cutoff);
                $isStaleByDate = $beforeDate !== null && $attendanceDate < $beforeDate;

                if (! $isStaleByAge && ! $isStaleByDate) {
                    continue;
                }

                if ($dryRun) {
                    $closed++;
                    continue;
                }

                DB::transaction(function () use ($session, $maxAgeHours) {
                    $note = trim((string) $session->notes);
                    $suffix = 'abandoned:stale_open>'.$maxAgeHours.'h';
                    $session->forceFill([
                        'end_time' => substr((string) $session->start_time, 0, 8),
                        'duration' => 0,
                        'status' => 'closed',
                        'notes' => $note === '' ? $suffix : $note.' | '.$suffix,
                    ])->save();
                });

                $closed++;
            }
        });

        $summary = [
            'scanned' => $scanned,
            'closed' => $closed,
            'dry_run' => $dryRun,
            'max_age_hours' => $maxAgeHours,
        ];

        Log::info('zkteco.stale_open_sessions_closed', $summary);

        return $summary;
    }
}
