<?php

namespace App\Console\Commands;

use App\Http\Controllers\ProjectEditLockController;
use App\Models\ProjectEditLock;
use Illuminate\Console\Command;

/**
 * Tidies stale `project_edit_locks` rows — locks whose holder stopped
 * heartbeating (closed the tab / lost the network) more than `LOCK_TTL`
 * (45s) ago. Correctness does NOT depend on this sweep: acquire / takeover /
 * the write guard all treat a stale lock as free on read (expiry-on-read).
 * This just stops abandoned rows accumulating.
 *
 * Bounded by `--max-time` so it stays under PHP's shared-hosting
 * max_execution_time, matching the other maintenance sweeps. Runs from the
 * single `schedule:run` cron.
 */
class PruneEditLocks extends Command
{
    protected $signature = 'projects:prune-edit-locks
                            {--max-time=55 : Stop after N seconds — keeps the cron under PHP max_execution_time}
                            {--chunk=1000 : Rows per delete batch}
                            {--dry-run : Count what would be deleted; do not delete}';

    protected $description = 'Prune stale project edit-lock rows (holder stopped heartbeating past the TTL).';

    public function handle(): int
    {
        $maxSeconds = max(1, (int) $this->option('max-time'));
        $chunk = max(1, (int) $this->option('chunk'));
        $dry = (bool) $this->option('dry-run');

        $cutoff = now()->subSeconds(ProjectEditLockController::LOCK_TTL);

        $eligible = fn () => ProjectEditLock::query()
            ->where(function ($q) use ($cutoff) {
                $q->whereNull('last_heartbeat_at')
                    ->orWhere('last_heartbeat_at', '<', $cutoff);
            });

        $totalEligible = (int) $eligible()->count();

        $this->line("Cutoff: {$cutoff->toIso8601String()} (TTL ".ProjectEditLockController::LOCK_TTL.'s)');
        $this->line("Eligible rows: {$totalEligible}");

        if ($dry || $totalEligible === 0) {
            return self::SUCCESS;
        }

        $deadline = microtime(true) + $maxSeconds;
        $deleted = 0;

        while (microtime(true) < $deadline) {
            $ids = $eligible()->limit($chunk)->pluck('id');
            if ($ids->isEmpty()) {
                break;
            }
            $deleted += ProjectEditLock::query()->whereIn('id', $ids)->delete();
        }

        $remaining = $totalEligible - $deleted;
        $this->line("Deleted: {$deleted}");
        if ($remaining > 0) {
            $this->warn("Remaining (will run next tick): {$remaining}");
        }

        return self::SUCCESS;
    }
}
