<?php

use App\Modules\Admin\Billing\Services\RenewalReminderService;
use App\Modules\Admin\Operations\Services\BackupHealthService;
use App\Modules\Admin\Operations\Services\DeploymentReadinessService;
use App\Modules\Admin\Operations\Services\OperationalAnomalyService;
use App\Modules\Admin\Operations\Services\RetentionPolicyService;
use App\Modules\Admin\Tenancy\Jobs\DispatchTrialExpiringRemindersJob;
use App\Modules\Admin\Tenancy\Models\Tenant;
use App\Modules\Admin\Tenancy\Services\SubscriptionLifecycleService;
use App\Modules\HR\Attendance\Jobs\GenerateAttendanceAbsencesJob;
use App\Modules\HR\Attendance\Jobs\RunAttendanceExceptionDetectionJob;
use App\Modules\HR\Attendance\Services\MissingCheckoutReminderService;
use App\Modules\Integrations\Platform\Services\ApiTokenService;
use App\Modules\Integrations\Platform\Services\IntegrationEventReplayService;
use App\Modules\Integrations\Platform\Services\WebhookDeliveryRetryService;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

Artisan::command('tenancy:notify-trial-expiring', function (): void {
    DispatchTrialExpiringRemindersJob::dispatch(3);
    $this->info('Trial expiry reminder job queued.');
})->purpose('Queue trial-expiry notifications for tenant admins');

Artisan::command('tenancy:sync-subscription-states', function (SubscriptionLifecycleService $lifecycle): void {
    $n = $lifecycle->runScheduledTransitions();
    $this->info('Subscription transitions applied: '.$n.'.');
})->purpose('Advance subscriptions through trial, grace, and expired states');

Artisan::command('billing:renewal-reminders {--days=7}', function (RenewalReminderService $renewals): void {
    $n = $renewals->dispatchRenewalReminders((int) $this->option('days'));
    $this->info('Renewal reminder audit rows written: '.$n.'.');
})->purpose('Record billing audit entries for subscriptions nearing renewal');

Artisan::command('attendance:detect-exceptions {tenant? : Tenant ID (omit for all active tenants)}', function (
    MissingCheckoutReminderService $reminders,
    ?string $tenant = null,
): void {
    $days = (int) config('attendance.exception_detection_lookback_days', 7);
    $to = now()->toDateString();
    $from = now()->subDays(max(1, $days))->toDateString();
    $q = Tenant::query()->where('is_active', true);
    if ($tenant !== null) {
        $q->whereKey((int) $tenant);
    }
    $n = 0;
    $reminderCount = 0;
    foreach ($q->cursor() as $t) {
        $tenantToday = now($t->timezone ?: config('app.timezone'))->toDateString();
        $reminderCount += $reminders->remindTenant((int) $t->id, $tenantToday);
        RunAttendanceExceptionDetectionJob::dispatch((int) $t->id, $from, $to);
        $n++;
    }
    $this->info('Queued detection for '.$n.' tenant(s); sent '.$reminderCount.' missing-checkout reminder(s).');
})->purpose('Queue attendance exception detection for tenant(s)');

Artisan::command('attendance:generate-absences {tenant? : Tenant ID (omit for all active tenants)} {--date= : Y-m-d (default: yesterday in tenant TZ)}', function (
    ?string $tenant = null,
): void {
    $q = Tenant::query()->where('is_active', true);
    if ($tenant !== null) {
        $q->whereKey((int) $tenant);
    }
    $n = 0;
    foreach ($q->cursor() as $t) {
        $tz = $t->timezone ?: config('app.timezone');
        $date = $this->option('date') ?: now($tz)->subDay()->toDateString();
        GenerateAttendanceAbsencesJob::dispatch((int) $t->id, $date);
        $n++;
    }
    $this->info('Queued absence generation for '.$n.' tenant(s).');
})->purpose('Queue auto-absence rows for working days without punches');

Artisan::command('ops:retention-cleanup', function (RetentionPolicyService $retention): void {
    $res = $retention->run();
    $this->info('Retention cleanup: '.json_encode($res));
})->purpose('Apply configured retention policies');

Artisan::command('ops:anomaly-scan', function (OperationalAnomalyService $anomalies): void {
    $n = $anomalies->scanAndNotify();
    $this->info('Operational alerts emitted: '.$n);
})->purpose('Scan for operational anomalies and alert admins');

Artisan::command('ops:readiness', function (DeploymentReadinessService $readiness): int {
    $r = $readiness->evaluate();
    foreach ($r['checks'] as $c) {
        $this->line(($c['ok'] ? '[OK] ' : '[FAIL] ').$c['key'].($c['detail'] ? ' — '.$c['detail'] : ''));
    }

    return $r['ok'] ? 0 : 1;
})->purpose('Deployment readiness checks (non-zero exit on failure)');

Artisan::command('ops:backup:record-success {type : db|files}', function (string $type, BackupHealthService $backups): void {
    $backups->recordSuccess($type, []);
    $this->info('Backup success recorded for '.$type);
})->purpose('Record a successful backup run (ops instrumentation)');

Artisan::command('ops:backup:record-failure {type : db|files} {message}', function (string $type, string $message, BackupHealthService $backups): void {
    $backups->recordFailure($type, $message, []);
    $this->info('Backup failure recorded for '.$type);
})->purpose('Record a failed backup run (ops instrumentation)');

Artisan::command('ops:backup:verify-restore {type : db|files} {runId?}', function (string $type, ?string $runId, BackupHealthService $backups): void {
    $row = $backups->markRestoreVerified($type, $runId !== null ? (int) $runId : null);
    $this->info($row ? 'Restore verified for run '.$row->id : 'No backup run found.');
})->purpose('Mark the last (or specified) backup run as restore-verified');

Artisan::command('api:token:create {tenantId : Tenant ID} {name : Token display name} {--scopes= : Comma-separated scopes} {--expires= : Expiration (Y-m-d or ISO8601)}', function (
    string $tenantId,
    string $name,
    ApiTokenService $tokens,
): void {
    $scopes = (string) $this->option('scopes');
    $expires = (string) $this->option('expires');

    $scopeList = array_values(array_filter(array_map('trim', explode(',', $scopes))));
    $expiresAt = $expires !== '' ? new DateTimeImmutable($expires) : null;

    $res = $tokens->createToken((int) $tenantId, $name, $scopeList, null, $expiresAt);

    $this->info('API token created (store this token once, it will not be shown again):');
    $this->line($res['plain']);
})->purpose('Create a tenant-scoped API token');

Artisan::command('ops:webhooks:retry-failed {--limit=200}', function (WebhookDeliveryRetryService $retry): void {
    $n = $retry->retryDeadLetters((int) $this->option('limit'));
    $this->info('Re-queued dead-letter webhook deliveries: '.$n.'.');
})->purpose('Retry dead-letter webhook deliveries (operational recovery)');

Artisan::command('ops:events:replay {eventId : Integration event UUID}', function (
    string $eventId,
    IntegrationEventReplayService $replay,
): void {
    $n = $replay->replay($eventId);
    $this->info('Outbound webhook deliveries queued for replay: '.$n.'.');
})->purpose('Replay outbound webhooks for an integration event');

Artisan::command('ops:webhooks:dead-letter-inspect {--limit=50}', function (): void {
    $rows = DB::table('integration_webhook_deliveries as d')
        ->join('integration_webhooks as w', 'w.id', '=', 'd.webhook_id')
        ->where('d.status', 'dead_letter')
        ->orderByDesc('d.dead_lettered_at')
        ->limit((int) $this->option('limit'))
        ->get(['d.id', 'd.tenant_id', 'd.webhook_id', 'd.event_id', 'd.attempt_count', 'd.dead_lettered_at', 'w.name as webhook_name']);

    foreach ($rows as $r) {
        $this->line(json_encode((array) $r, JSON_UNESCAPED_SLASHES));
    }
})->purpose('Inspect dead-letter webhook deliveries (JSON lines)');
