<?php

namespace App\Services\BackupScheduling;

use App\Jobs\RunBackupScheduleJob;
use App\Models\BackupSchedule;
use App\Models\ManagedEnvironment;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Support\OperationRunType;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log;

class BackupScheduleDispatcher
{
    public function __construct(
        private readonly ScheduleTimeService $scheduleTimeService,
        private readonly AuditLogger $auditLogger,
        private readonly OperationRunService $operationRunService,
    ) {}

    /**
     * Dispatch due schedules.
     *
     * No catch-up policy: we only dispatch if the current minute-slot is due.
     *
     * @return array{created_runs:int, skipped_runs:int, scanned_schedules:int}
     */
    public function dispatchDue(?array $tenantIdentifiers = null): array
    {
        $nowUtc = CarbonImmutable::now('UTC');

        $schedulesQuery = BackupSchedule::query()
            ->withoutTrashed()
            ->where('is_enabled', true)
            ->whereHas('tenant', fn ($query) => $query->where('lifecycle_status', ManagedEnvironment::STATUS_ACTIVE))
            ->with('tenant');

        if (is_array($tenantIdentifiers) && ! empty($tenantIdentifiers)) {
            $schedulesQuery->whereIn('managed_environment_id', $this->resolveTenantIds($tenantIdentifiers));
        }

        $createdRuns = 0;
        $skippedRuns = 0;
        $scannedSchedules = 0;

        foreach ($schedulesQuery->cursor() as $schedule) {
            $scannedSchedules++;

            $slot = $this->scheduleTimeService->nextRunFor($schedule, $nowUtc->subMinute());

            if ($slot === null) {
                $schedule->forceFill(['next_run_at' => null])->saveQuietly();

                continue;
            }

            if ($slot->greaterThan($nowUtc)) {
                if (! $schedule->next_run_at || ! $schedule->next_run_at->equalTo($slot)) {
                    $schedule->forceFill(['next_run_at' => $slot])->saveQuietly();
                }

                continue;
            }

            $scheduledFor = $slot->startOfMinute();

            $operationRun = $this->operationRunService->ensureRunWithIdentityStrict(
                tenant: $schedule->tenant,
                type: OperationRunType::BackupScheduleExecute->value,
                identityInputs: [
                    'backup_schedule_id' => (int) $schedule->id,
                    'scheduled_for' => $scheduledFor->toDateTimeString(),
                ],
                context: [
                    'backup_schedule_id' => (int) $schedule->id,
                    'scheduled_for' => $scheduledFor->toDateTimeString(),
                    'trigger' => 'scheduled',
                ],
            );

            if (! $operationRun->wasRecentlyCreated) {
                $skippedRuns++;

                Log::debug('Backup schedule operation already dispatched for slot.', [
                    'schedule_id' => $schedule->id,
                    'scheduled_for' => $scheduledFor->toDateTimeString(),
                    'operation_run_id' => $operationRun->getKey(),
                ]);

                $schedule->forceFill([
                    'next_run_at' => $this->scheduleTimeService->nextRunFor($schedule, $nowUtc),
                ])->saveQuietly();

                continue;
            }

            $createdRuns++;

            $this->auditLogger->log(
                tenant: $schedule->tenant,
                action: 'backup_schedule.run_dispatched',
                context: [
                    'metadata' => [
                        'backup_schedule_id' => $schedule->id,
                        'operation_run_id' => $operationRun->getKey(),
                        'scheduled_for' => $scheduledFor->toDateTimeString(),
                    ],
                ],
                resourceType: 'operation_run',
                resourceId: (string) $operationRun->getKey(),
                status: 'success'
            );

            $schedule->forceFill([
                'next_run_at' => $this->scheduleTimeService->nextRunFor($schedule, $nowUtc),
            ])->saveQuietly();

            Bus::dispatch(new RunBackupScheduleJob(operationRun: $operationRun, backupScheduleId: (int) $schedule->id));
        }

        return [
            'created_runs' => $createdRuns,
            'skipped_runs' => $skippedRuns,
            'scanned_schedules' => $scannedSchedules,
        ];
    }

    /**
     * @param  array<int, string>  $tenantIdentifiers
     * @return array<int>
     */
    private function resolveTenantIds(array $tenantIdentifiers): array
    {
        $tenantIds = [];

        foreach ($tenantIdentifiers as $identifier) {
            $tenant = ManagedEnvironment::query()
                ->where('lifecycle_status', ManagedEnvironment::STATUS_ACTIVE)
                ->forTenant($identifier)
                ->first();

            if ($tenant) {
                $tenantIds[] = $tenant->id;
            }
        }

        return array_values(array_unique($tenantIds));
    }
}
