<?php

declare(strict_types=1);

namespace App\Filament\Pages\Workspaces;

use App\Filament\Pages\TenantDashboard;
use App\Filament\Support\VerificationReportChangeIndicator;
use App\Filament\Support\VerificationReportViewer;
use App\Jobs\ProviderComplianceSnapshotJob;
use App\Jobs\ProviderConnectionHealthCheckJob;
use App\Jobs\ProviderInventorySyncJob;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\Tenant;
use App\Models\TenantMembership;
use App\Models\TenantOnboardingSession;
use App\Models\User;
use App\Models\VerificationCheckAcknowledgement;
use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Auth\TenantMembershipManager;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Services\Providers\CredentialManager;
use App\Services\Providers\ProviderOperationRegistry;
use App\Services\Providers\ProviderOperationStartGate;
use App\Services\Verification\VerificationCheckAcknowledgementService;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\Verification\VerificationCheckStatus;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Action;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Radio;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ViewField;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions as SchemaActions;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Text;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Wizard;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Width;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ManagedTenantOnboardingWizard extends Page
{
    protected static string $layout = 'filament-panels::components.layout.simple';

    protected Width|string|null $maxContentWidth = Width::ScreenExtraLarge;

    protected static bool $shouldRegisterNavigation = false;

    protected static bool $isDiscovered = false;

    protected static ?string $title = 'Managed tenant onboarding';

    protected static ?string $slug = 'onboarding';

    /**
     * Disable the simple-layout topbar to prevent lazy-loaded
     * DatabaseNotifications from triggering Livewire update 404s
     * on this workspace-scoped route.
     */
    protected function getLayoutData(): array
    {
        return [
            'hasTopbar' => false,
        ];
    }

    public Workspace $workspace;

    public ?Tenant $managedTenant = null;

    public ?TenantOnboardingSession $onboardingSession = null;

    public ?int $selectedProviderConnectionId = null;

    /**
     * Filament schema state.
     *
     * @var array<string, mixed>
     */
    public array $data = [];

    /**
     * @var array<int, string>
     */
    public array $selectedBootstrapOperationTypes = [];

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        return [];
    }

    public function mount(): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());

        if ($workspaceId === null) {
            $this->redirect('/admin/choose-workspace');

            return;
        }

        $workspace = Workspace::query()->whereKey($workspaceId)->first();

        if (! $workspace instanceof Workspace) {
            abort(404);
        }

        if (! app(WorkspaceContext::class)->isMember($user, $workspace)) {
            abort(404);
        }

        $this->workspace = $workspace;

        $this->resumeLatestOnboardingSessionIfUnambiguous();

        $this->initializeWizardData();
    }

    public function content(Schema $schema): Schema
    {
        return $schema
            ->statePath('data')
            ->schema([
                Wizard::make([
                    Step::make('Identify managed tenant')
                        ->description('Create or resume a managed tenant in this workspace.')
                        ->schema([
                            Section::make('Tenant')
                                ->schema([
                                    TextInput::make('entra_tenant_id')
                                        ->label('Entra Tenant ID (GUID)')
                                        ->required()
                                        ->rules(['uuid'])
                                        ->maxLength(255),
                                    Select::make('environment')
                                        ->label('Environment')
                                        ->required()
                                        ->options([
                                            'prod' => 'Production',
                                            'staging' => 'Staging',
                                            'dev' => 'Development',
                                            'other' => 'Other',
                                        ])
                                        ->default('other'),
                                    TextInput::make('name')
                                        ->label('Display name')
                                        ->required()
                                        ->maxLength(255),
                                    TextInput::make('primary_domain')
                                        ->label('Primary domain (optional)')
                                        ->maxLength(255),
                                    Textarea::make('notes')
                                        ->label('Notes (optional)')
                                        ->rows(3)
                                        ->maxLength(2000),
                                ]),
                        ])
                        ->afterValidation(function (): void {
                            $entraTenantId = (string) ($this->data['entra_tenant_id'] ?? '');
                            $environment = (string) ($this->data['environment'] ?? 'other');
                            $tenantName = (string) ($this->data['name'] ?? '');
                            $primaryDomain = (string) ($this->data['primary_domain'] ?? '');
                            $notes = (string) ($this->data['notes'] ?? '');

                            try {
                                $this->identifyManagedTenant([
                                    'entra_tenant_id' => $entraTenantId,
                                    'environment' => $environment,
                                    'name' => $tenantName,
                                    'primary_domain' => $primaryDomain,
                                    'notes' => $notes,
                                ]);
                            } catch (NotFoundHttpException) {
                                Notification::make()
                                    ->title('Tenant not available')
                                    ->body('This tenant cannot be onboarded in this workspace.')
                                    ->danger()
                                    ->send();

                                throw new Halt;
                            }

                            $this->initializeWizardData();
                        }),

                    Step::make('Provider connection')
                        ->description('Select an existing connection or create a new one.')
                        ->schema([
                            Section::make('Connection')
                                ->schema([
                                    Radio::make('connection_mode')
                                        ->label('Mode')
                                        ->options([
                                            'existing' => 'Use existing connection',
                                            'new' => 'Create new connection',
                                        ])
                                        ->required()
                                        ->default('existing')
                                        ->live(),
                                    Select::make('provider_connection_id')
                                        ->label('Provider connection')
                                        ->required(fn (Get $get): bool => $get('connection_mode') === 'existing')
                                        ->options(fn (): array => $this->providerConnectionOptions())
                                        ->visible(fn (Get $get): bool => $get('connection_mode') === 'existing')
                                        ->hintActions([
                                            Action::make('edit_selected_connection')
                                                ->label('Edit selected connection')
                                                ->icon('heroicon-m-pencil-square')
                                                ->color('gray')
                                                ->slideOver()
                                                ->modalHeading('Edit provider connection')
                                                ->modalDescription('Changes apply to this workspace connection.')
                                                ->modalSubmitActionLabel('Save changes')
                                                ->closeModalByClickingAway(false)
                                                ->visible(fn (Get $get): bool => $get('connection_mode') === 'existing'
                                                    && is_numeric($get('provider_connection_id')))
                                                ->disabled(fn (): bool => ! $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE))
                                                ->tooltip(fn (): ?string => $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE)
                                                    ? null
                                                    : 'You don\'t have permission to edit connections.')
                                                ->fillForm(function (Get $get): array {
                                                    $recordId = $get('provider_connection_id');

                                                    if (! is_numeric($recordId)) {
                                                        return [];
                                                    }

                                                    return $this->inlineEditSelectedConnectionFill((int) $recordId);
                                                })
                                                ->form([
                                                    TextInput::make('display_name')
                                                        ->label('Connection name')
                                                        ->required()
                                                        ->maxLength(255),
                                                    TextInput::make('entra_tenant_id')
                                                        ->label('Directory (tenant) ID')
                                                        ->disabled()
                                                        ->dehydrated(false),
                                                    TextInput::make('client_id')
                                                        ->label('App (client) ID')
                                                        ->required()
                                                        ->maxLength(255)
                                                        ->helperText('The client secret is not editable here.'),
                                                ])
                                                ->action(function (array $data, Get $get): void {
                                                    $recordId = $get('provider_connection_id');

                                                    if (! is_numeric($recordId)) {
                                                        abort(404);
                                                    }

                                                    $this->updateSelectedProviderConnectionInline((int) $recordId, $data);
                                                }),
                                        ]),

                                    TextInput::make('new_connection.display_name')
                                        ->label('Display name')
                                        ->required(fn (Get $get): bool => $get('connection_mode') === 'new')
                                        ->maxLength(255)
                                        ->visible(fn (Get $get): bool => $get('connection_mode') === 'new'),
                                    TextInput::make('new_connection.client_id')
                                        ->label('Client ID')
                                        ->required(fn (Get $get): bool => $get('connection_mode') === 'new')
                                        ->maxLength(255)
                                        ->visible(fn (Get $get): bool => $get('connection_mode') === 'new'),
                                    TextInput::make('new_connection.client_secret')
                                        ->label('Client secret')
                                        ->password()
                                        ->required(fn (Get $get): bool => $get('connection_mode') === 'new')
                                        ->maxLength(255)
                                        ->visible(fn (Get $get): bool => $get('connection_mode') === 'new')
                                        ->helperText('Stored encrypted and never shown again.'),
                                    Toggle::make('new_connection.is_default')
                                        ->label('Make default')
                                        ->default(true)
                                        ->visible(fn (Get $get): bool => $get('connection_mode') === 'new'),
                                ]),
                        ])
                        ->afterValidation(function (): void {
                            if (! $this->managedTenant instanceof Tenant) {
                                throw new Halt;
                            }

                            $mode = (string) ($this->data['connection_mode'] ?? 'existing');

                            if ($mode === 'new') {
                                $new = is_array($this->data['new_connection'] ?? null) ? $this->data['new_connection'] : [];

                                $this->createProviderConnection([
                                    'display_name' => (string) ($new['display_name'] ?? ''),
                                    'client_id' => (string) ($new['client_id'] ?? ''),
                                    'client_secret' => (string) ($new['client_secret'] ?? ''),
                                    'is_default' => (bool) ($new['is_default'] ?? true),
                                ]);

                                if (is_array($this->data['new_connection'] ?? null)) {
                                    $this->data['new_connection']['client_secret'] = null;
                                }
                            } else {
                                $providerConnectionId = (int) ($this->data['provider_connection_id'] ?? 0);

                                if ($providerConnectionId <= 0) {
                                    throw new Halt;
                                }

                                $this->selectProviderConnection($providerConnectionId);
                            }

                            $this->touchOnboardingSessionStep('connection');
                            $this->initializeWizardData();
                        }),

                    Step::make('Verify access')
                        ->description('Run a queued verification check (Operation Run).')
                        ->schema([
                            Section::make('Verification')
                                ->schema([
                                    Text::make(fn (): string => 'Status: '.$this->verificationStatusLabel())
                                        ->badge()
                                        ->color(fn (): string => $this->verificationStatusColor()),
                                    Text::make('Connection updated — re-run verification to refresh results.')
                                        ->visible(fn (): bool => $this->connectionRecentlyUpdated()),
                                    Text::make('The selected provider connection has changed since this verification run. Start verification again to validate the current connection.')
                                        ->visible(fn (): bool => $this->verificationRunIsStaleForSelectedConnection()),
                                    Text::make('Verification is in progress. Use “Refresh results” to see the latest stored status.')
                                        ->visible(fn (): bool => $this->verificationStatus() === 'in_progress'),
                                    SchemaActions::make([
                                        Action::make('wizardStartVerification')
                                            ->label('Start verification')
                                            ->visible(fn (): bool => $this->managedTenant instanceof Tenant && ! $this->verificationRunIsActive())
                                            ->disabled(fn (): bool => ! $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START))
                                            ->tooltip(fn (): ?string => $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START)
                                                ? null
                                                : 'You do not have permission to start verification.')
                                            ->action(fn () => $this->startVerification()),
                                        Action::make('wizardRefreshVerification')
                                            ->label('Refresh')
                                            ->visible(fn (): bool => $this->verificationRunUrl() !== null && $this->verificationStatus() === 'in_progress')
                                            ->action(fn () => $this->refreshVerificationStatus()),
                                    ]),
                                    ViewField::make('verification_report')
                                        ->label('')
                                        ->default(null)
                                        ->view('filament.forms.components.managed-tenant-onboarding-verification-report')
                                        ->viewData(fn (): array => $this->verificationReportViewData())
                                        ->visible(fn (): bool => $this->verificationRunUrl() !== null),
                                ]),
                        ])
                        ->beforeValidation(function (): void {
                            if (! $this->verificationHasSucceeded()) {
                                Notification::make()
                                    ->title('Verification required')
                                    ->body('Run verification successfully before continuing.')
                                    ->warning()
                                    ->send();

                                throw new Halt;
                            }

                            $this->touchOnboardingSessionStep('verify');
                        }),

                    Step::make('Bootstrap (optional)')
                        ->description('Optionally start inventory and compliance operations.')
                        ->schema([
                            Section::make('Bootstrap')
                                ->schema([
                                    CheckboxList::make('bootstrap_operation_types')
                                        ->label('Bootstrap actions')
                                        ->options(fn (): array => $this->bootstrapOperationOptions())
                                        ->columns(1),
                                    SchemaActions::make([
                                        Action::make('wizardStartBootstrap')
                                            ->label('Start bootstrap')
                                            ->visible(fn (): bool => $this->managedTenant instanceof Tenant)
                                            ->disabled(fn (): bool => ! $this->canStartAnyBootstrap())
                                            ->tooltip(fn (): ?string => $this->canStartAnyBootstrap()
                                                ? null
                                                : 'You do not have permission to start bootstrap actions.')
                                            ->action(fn () => $this->startBootstrap((array) ($this->data['bootstrap_operation_types'] ?? []))),
                                    ]),
                                    Text::make(fn (): string => $this->bootstrapRunsLabel())
                                        ->hidden(fn (): bool => $this->bootstrapRunsLabel() === ''),
                                ]),
                        ])
                        ->afterValidation(function (): void {
                            $types = $this->data['bootstrap_operation_types'] ?? [];
                            $this->selectedBootstrapOperationTypes = is_array($types)
                                ? array_values(array_filter($types, static fn ($v): bool => is_string($v) && $v !== ''))
                                : [];

                            $this->touchOnboardingSessionStep('bootstrap');
                        }),

                    Step::make('Complete')
                        ->description('Activate the tenant and finish onboarding.')
                        ->schema([
                            Section::make('Finish')
                                ->schema([
                                    Text::make(fn (): string => $this->managedTenant instanceof Tenant
                                        ? 'Tenant: '.$this->managedTenant->name
                                        : 'Tenant: not selected')
                                        ->badge()
                                        ->color('gray'),
                                    Text::make(fn (): string => 'Verification: '.$this->verificationStatusLabel())
                                        ->badge()
                                        ->color(fn (): string => $this->verificationStatusColor()),
                                    Toggle::make('override_blocked')
                                        ->label('Override blocked verification')
                                        ->helperText('Owner-only. Requires a reason and will be recorded in the audit log.')
                                        ->visible(fn (): bool => $this->verificationStatus() === 'blocked'
                                            && $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_ACTIVATE)),
                                    Textarea::make('override_reason')
                                        ->label('Override reason')
                                        ->required(fn (Get $get): bool => (bool) $get('override_blocked'))
                                        ->visible(fn (Get $get): bool => (bool) $get('override_blocked'))
                                        ->rows(3)
                                        ->maxLength(500),
                                    SchemaActions::make([
                                        Action::make('wizardCompleteOnboarding')
                                            ->label('Complete onboarding')
                                            ->color('success')
                                            ->disabled(fn (): bool => ! $this->canCompleteOnboarding()
                                                || ! $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_ACTIVATE))
                                            ->tooltip(fn (): ?string => $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_ACTIVATE)
                                                ? null
                                                : 'Owner required to activate.')
                                            ->action(fn () => $this->completeOnboarding()),
                                    ]),
                                ]),
                        ])
                        ->beforeValidation(function (): void {
                            if (! $this->canCompleteOnboarding()) {
                                throw new Halt;
                            }
                        }),
                ])
                    ->startOnStep(fn (): int => $this->computeWizardStartStep())
                    ->skippable(false),
            ]);
    }

    private function resumeLatestOnboardingSessionIfUnambiguous(): void
    {
        $sessionCount = TenantOnboardingSession::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->whereNull('completed_at')
            ->count();

        if ($sessionCount !== 1) {
            return;
        }

        $session = TenantOnboardingSession::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->whereNull('completed_at')
            ->orderByDesc('updated_at')
            ->first();

        if (! $session instanceof TenantOnboardingSession) {
            return;
        }

        $tenant = Tenant::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->whereKey((int) $session->tenant_id)
            ->first();

        if (! $tenant instanceof Tenant) {
            return;
        }

        $this->managedTenant = $tenant;
        $this->onboardingSession = $session;

        $providerConnectionId = $session->state['provider_connection_id'] ?? null;
        $this->selectedProviderConnectionId = is_int($providerConnectionId)
            ? $providerConnectionId
            : $this->resolveDefaultProviderConnectionId($tenant);

        $bootstrapTypes = $session->state['bootstrap_operation_types'] ?? [];
        $this->selectedBootstrapOperationTypes = is_array($bootstrapTypes)
            ? array_values(array_filter($bootstrapTypes, static fn ($v): bool => is_string($v) && $v !== ''))
            : [];
    }

    private function initializeWizardData(): void
    {
        // Ensure all entangled schema state paths exist at render time.
        // Livewire v4 can throw when entangling to missing nested array keys.
        $this->data['notes'] ??= '';
        $this->data['override_blocked'] ??= false;
        $this->data['override_reason'] ??= '';

        if (! array_key_exists('connection_mode', $this->data)) {
            $this->data['connection_mode'] = 'existing';
        }

        if ($this->managedTenant instanceof Tenant) {
            $this->data['entra_tenant_id'] ??= (string) $this->managedTenant->tenant_id;
            $this->data['environment'] ??= (string) ($this->managedTenant->environment ?? 'other');
            $this->data['name'] ??= (string) $this->managedTenant->name;
            $this->data['primary_domain'] ??= (string) ($this->managedTenant->domain ?? '');

            $notes = is_array($this->managedTenant->metadata) ? ($this->managedTenant->metadata['notes'] ?? null) : null;
            if (is_string($notes) && trim($notes) !== '') {
                $this->data['notes'] ??= trim($notes);
            }
        }

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $state = is_array($this->onboardingSession->state) ? $this->onboardingSession->state : [];

            if (isset($state['entra_tenant_id']) && is_string($state['entra_tenant_id']) && trim($state['entra_tenant_id']) !== '') {
                $this->data['entra_tenant_id'] ??= trim($state['entra_tenant_id']);
            }

            if (isset($state['environment']) && is_string($state['environment']) && trim($state['environment']) !== '') {
                $this->data['environment'] ??= trim($state['environment']);
            }

            if (isset($state['tenant_name']) && is_string($state['tenant_name']) && trim($state['tenant_name']) !== '') {
                $this->data['name'] ??= trim($state['tenant_name']);
            }

            if (array_key_exists('primary_domain', $state)) {
                $domain = $state['primary_domain'];

                if (is_string($domain)) {
                    $this->data['primary_domain'] ??= $domain;
                }
            }

            if (array_key_exists('notes', $state)) {
                $notes = $state['notes'];

                if (is_string($notes)) {
                    $this->data['notes'] ??= $notes;
                }
            }

            $providerConnectionId = $this->onboardingSession->state['provider_connection_id'] ?? null;
            if (is_int($providerConnectionId)) {
                $this->data['provider_connection_id'] = $providerConnectionId;
                $this->selectedProviderConnectionId = $providerConnectionId;
            }

            $types = $this->onboardingSession->state['bootstrap_operation_types'] ?? null;
            if (is_array($types)) {
                $this->data['bootstrap_operation_types'] = array_values(array_filter($types, static fn ($v): bool => is_string($v) && $v !== ''));
            }
        }

        if (($this->data['provider_connection_id'] ?? null) === null && $this->selectedProviderConnectionId !== null) {
            $this->data['provider_connection_id'] = $this->selectedProviderConnectionId;
        }
    }

    private function computeWizardStartStep(): int
    {
        if (! $this->managedTenant instanceof Tenant) {
            return 1;
        }

        if (! $this->resolveSelectedProviderConnection($this->managedTenant)) {
            return 2;
        }

        if (! $this->verificationHasSucceeded()) {
            return 3;
        }

        return 4;
    }

    /**
     * @return array<int, string>
     */
    private function providerConnectionOptions(): array
    {
        if (! $this->managedTenant instanceof Tenant) {
            return [];
        }

        return ProviderConnection::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', $this->managedTenant->getKey())
            ->orderByDesc('is_default')
            ->orderBy('display_name')
            ->pluck('display_name', 'id')
            ->all();
    }

    private function verificationStatusLabel(): string
    {
        return BadgeCatalog::spec(
            BadgeDomain::ManagedTenantOnboardingVerificationStatus,
            $this->verificationStatus(),
        )->label;
    }

    private function verificationStatus(): string
    {
        $run = $this->verificationRun();

        if (! $run instanceof OperationRun) {
            return 'not_started';
        }

        if (! $this->verificationRunMatchesSelectedConnection($run)) {
            return 'needs_attention';
        }

        if ($run->status !== OperationRunStatus::Completed->value) {
            return 'in_progress';
        }

        if ($run->outcome === OperationRunOutcome::Blocked->value) {
            return 'blocked';
        }

        if ($run->outcome === OperationRunOutcome::Succeeded->value) {
            return 'ready';
        }

        if ($run->outcome === OperationRunOutcome::PartiallySucceeded->value) {
            return 'needs_attention';
        }

        if ($run->outcome !== OperationRunOutcome::Failed->value) {
            return 'needs_attention';
        }

        $failures = is_array($run->failure_summary ?? null) ? $run->failure_summary : [];

        if ($failures === []) {
            return 'blocked';
        }

        foreach ($failures as $failure) {
            if (! is_array($failure)) {
                continue;
            }

            $reasonCode = $failure['reason_code'] ?? null;

            if (! is_string($reasonCode) || $reasonCode === '') {
                continue;
            }

            if (in_array($reasonCode, ['provider_auth_failed', 'provider_permission_denied', 'permission_denied', 'provider_consent_missing'], true)) {
                return 'blocked';
            }
        }

        return 'needs_attention';
    }

    private function verificationRunIsActive(): bool
    {
        $run = $this->verificationRun();

        return $run instanceof OperationRun
            && $run->status !== OperationRunStatus::Completed->value;
    }

    private function verificationStatusColor(): string
    {
        return BadgeCatalog::spec(
            BadgeDomain::ManagedTenantOnboardingVerificationStatus,
            $this->verificationStatus(),
        )->color;
    }

    private function verificationRunUrl(): ?string
    {
        if (! $this->managedTenant instanceof Tenant) {
            return null;
        }

        if (! $this->onboardingSession instanceof TenantOnboardingSession) {
            return null;
        }

        $runId = $this->onboardingSession->state['verification_operation_run_id'] ?? null;

        if (! is_int($runId)) {
            return null;
        }

        return $this->tenantlessOperationRunUrl($runId);
    }

    /**
     * @return array{
     *   run: array<string, mixed>|null,
     *   runUrl: string|null,
     *   report: array<string, mixed>|null,
     *   fingerprint: string|null,
     *   changeIndicator: array{state: 'no_changes'|'changed', previous_report_id: int}|null,
     *   previousRunUrl: string|null,
     *   canAcknowledge: bool,
     *   acknowledgements: array<string, array{
     *     check_key: string,
     *     ack_reason: string,
     *     acknowledged_at: string|null,
     *     expires_at: string|null,
     *     acknowledged_by: array{id: int, name: string}|null
     *   }>
     * }
     */
    private function verificationReportViewData(): array
    {
        $run = $this->verificationRun();
        $runUrl = $this->verificationRunUrl();

        if (! $run instanceof OperationRun) {
            return [
                'run' => null,
                'runUrl' => $runUrl,
                'report' => null,
                'fingerprint' => null,
                'changeIndicator' => null,
                'previousRunUrl' => null,
                'canAcknowledge' => false,
                'acknowledgements' => [],
            ];
        }

        $report = VerificationReportViewer::report($run);
        $fingerprint = is_array($report) ? VerificationReportViewer::fingerprint($report) : null;

        $changeIndicator = VerificationReportChangeIndicator::forRun($run);
        $previousRunUrl = $changeIndicator === null
            ? null
            : $this->tenantlessOperationRunUrl((int) $changeIndicator['previous_report_id']);

        $user = auth()->user();
        $canAcknowledge = $user instanceof User && $this->managedTenant instanceof Tenant
            ? $user->can(Capabilities::TENANT_VERIFICATION_ACKNOWLEDGE, $this->managedTenant)
            : false;

        $acknowledgements = VerificationCheckAcknowledgement::query()
            ->where('tenant_id', (int) $run->tenant_id)
            ->where('workspace_id', (int) $run->workspace_id)
            ->where('operation_run_id', (int) $run->getKey())
            ->with('acknowledgedByUser')
            ->get()
            ->mapWithKeys(static function (VerificationCheckAcknowledgement $ack): array {
                $user = $ack->acknowledgedByUser;

                return [
                    (string) $ack->check_key => [
                        'check_key' => (string) $ack->check_key,
                        'ack_reason' => (string) $ack->ack_reason,
                        'acknowledged_at' => $ack->acknowledged_at?->toJSON(),
                        'expires_at' => $ack->expires_at?->toJSON(),
                        'acknowledged_by' => $user instanceof User
                            ? [
                                'id' => (int) $user->getKey(),
                                'name' => (string) $user->name,
                            ]
                            : null,
                    ],
                ];
            })
            ->all();

        $context = is_array($run->context ?? null) ? $run->context : [];
        $targetScope = $context['target_scope'] ?? [];
        $targetScope = is_array($targetScope) ? $targetScope : [];

        $failures = is_array($run->failure_summary ?? null) ? $run->failure_summary : [];
        $verificationReport = VerificationReportViewer::report($run);

        return [
            'run' => [
                'id' => (int) $run->getKey(),
                'type' => (string) $run->type,
                'status' => (string) $run->status,
                'outcome' => (string) $run->outcome,
                'initiator_name' => (string) $run->initiator_name,
                'started_at' => $run->started_at?->toJSON(),
                'completed_at' => $run->completed_at?->toJSON(),
                'target_scope' => $targetScope,
                'failures' => $failures,
            ],
            'runUrl' => $runUrl,
            'report' => $report,
            'fingerprint' => $fingerprint,
            'changeIndicator' => $changeIndicator,
            'previousRunUrl' => $previousRunUrl,
            'canAcknowledge' => $canAcknowledge,
            'acknowledgements' => $acknowledgements,
        ];
    }

    public function acknowledgeVerificationCheckAction(): Action
    {
        return Action::make('acknowledgeVerificationCheck')
            ->label('Acknowledge')
            ->color('gray')
            ->requiresConfirmation()
            ->modalHeading('Acknowledge issue')
            ->modalDescription('This records an acknowledgement for governance and audit. It does not change the verification outcome.')
            ->form([
                Textarea::make('ack_reason')
                    ->label('Reason')
                    ->required()
                    ->maxLength(160)
                    ->rows(3),
                TextInput::make('expires_at')
                    ->label('Expiry (optional)')
                    ->helperText('Optional timestamp (informational only).')
                    ->maxLength(64),
            ])
            ->action(function (array $data, array $arguments): void {
                $user = auth()->user();

                if (! $user instanceof User) {
                    abort(403);
                }

                if (! $this->managedTenant instanceof Tenant) {
                    abort(404);
                }

                $tenant = $this->managedTenant->fresh();

                if (! $tenant instanceof Tenant) {
                    abort(404);
                }

                $run = $this->verificationRun();

                if (! $run instanceof OperationRun) {
                    throw new NotFoundHttpException;
                }

                $checkKey = (string) ($arguments['check_key'] ?? '');
                $ackReason = (string) ($data['ack_reason'] ?? '');
                $expiresAt = $data['expires_at'] ?? null;
                $expiresAt = is_string($expiresAt) ? $expiresAt : null;

                try {
                    app(VerificationCheckAcknowledgementService::class)->acknowledge(
                        tenant: $tenant,
                        run: $run,
                        checkKey: $checkKey,
                        ackReason: $ackReason,
                        expiresAt: $expiresAt,
                        actor: $user,
                    );
                } catch (InvalidArgumentException $e) {
                    Notification::make()
                        ->title('Unable to acknowledge')
                        ->body($e->getMessage())
                        ->danger()
                        ->send();

                    return;
                }

                Notification::make()
                    ->title('Issue acknowledged')
                    ->success()
                    ->send();
            })
            ->visible(function (array $arguments): bool {
                $user = auth()->user();

                if (! $user instanceof User) {
                    return false;
                }

                if (! $this->managedTenant instanceof Tenant) {
                    return false;
                }

                if (! $user->can(Capabilities::TENANT_VERIFICATION_ACKNOWLEDGE, $this->managedTenant)) {
                    return false;
                }

                $run = $this->verificationRun();

                if (! $run instanceof OperationRun) {
                    return false;
                }

                $checkKey = trim((string) ($arguments['check_key'] ?? ''));

                if ($checkKey === '') {
                    return false;
                }

                $ackExists = VerificationCheckAcknowledgement::query()
                    ->where('operation_run_id', (int) $run->getKey())
                    ->where('check_key', $checkKey)
                    ->exists();

                if ($ackExists) {
                    return false;
                }

                $report = VerificationReportViewer::report($run);

                if (! is_array($report)) {
                    return false;
                }

                $checks = $report['checks'] ?? null;
                $checks = is_array($checks) ? $checks : [];

                foreach ($checks as $check) {
                    if (! is_array($check)) {
                        continue;
                    }

                    if (($check['key'] ?? null) !== $checkKey) {
                        continue;
                    }

                    $status = $check['status'] ?? null;

                    return is_string($status) && in_array($status, [
                        VerificationCheckStatus::Fail->value,
                        VerificationCheckStatus::Warn->value,
                    ], true);
                }

                return false;
            });
    }

    private function bootstrapRunsLabel(): string
    {
        if (! $this->onboardingSession instanceof TenantOnboardingSession) {
            return '';
        }

        $runs = $this->onboardingSession->state['bootstrap_operation_runs'] ?? null;
        $runs = is_array($runs) ? $runs : [];

        if ($runs === []) {
            return '';
        }

        return sprintf('Started %d bootstrap run(s).', count($runs));
    }

    private function touchOnboardingSessionStep(string $step): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        if (! $this->onboardingSession instanceof TenantOnboardingSession) {
            return;
        }

        $this->onboardingSession->forceFill([
            'current_step' => $step,
            'updated_by_user_id' => (int) $user->getKey(),
        ])->save();
    }

    private function authorizeWorkspaceMutation(User $user, string $capability): void
    {
        $this->authorizeWorkspaceMember($user);

        if (! $user->can($capability, $this->workspace)) {
            abort(403);
        }
    }

    private function authorizeWorkspaceMember(User $user): void
    {
        if (! app(WorkspaceContext::class)->isMember($user, $this->workspace)) {
            abort(404);
        }
    }

    private function resolveWorkspaceIdForUnboundTenant(Tenant $tenant): ?int
    {
        $workspaceId = DB::table('tenant_memberships')
            ->join('workspace_memberships', 'workspace_memberships.user_id', '=', 'tenant_memberships.user_id')
            ->where('tenant_memberships.tenant_id', (int) $tenant->getKey())
            ->orderByRaw("CASE tenant_memberships.role WHEN 'owner' THEN 0 WHEN 'manager' THEN 1 WHEN 'operator' THEN 2 ELSE 3 END")
            ->value('workspace_memberships.workspace_id');

        return $workspaceId === null ? null : (int) $workspaceId;
    }

    /**
     * @param  array{entra_tenant_id: string, environment: string, name: string, primary_domain?: string, notes?: string}  $data
     */
    public function identifyManagedTenant(array $data): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_IDENTIFY);

        $entraTenantId = (string) ($data['entra_tenant_id'] ?? '');
        $tenantName = (string) ($data['name'] ?? '');
        $environment = (string) ($data['environment'] ?? 'other');
        $primaryDomain = trim((string) ($data['primary_domain'] ?? ''));
        $notes = trim((string) ($data['notes'] ?? ''));

        if ($entraTenantId === '' || $tenantName === '') {
            abort(422);
        }

        if (! in_array($environment, ['prod', 'staging', 'dev', 'other'], true)) {
            abort(422);
        }

        $primaryDomain = $primaryDomain !== '' ? $primaryDomain : null;
        $notes = $notes !== '' ? $notes : null;

        DB::transaction(function () use ($user, $entraTenantId, $tenantName, $environment, $primaryDomain, $notes): void {
            $auditLogger = app(WorkspaceAuditLogger::class);
            $membershipManager = app(TenantMembershipManager::class);

            $existingTenant = Tenant::query()
                ->withTrashed()
                ->where('tenant_id', $entraTenantId)
                ->first();

            if ($existingTenant instanceof Tenant) {
                if ($existingTenant->trashed() || $existingTenant->status === Tenant::STATUS_ARCHIVED) {
                    abort(404);
                }

                if ($existingTenant->workspace_id === null) {
                    $resolvedWorkspaceId = $this->resolveWorkspaceIdForUnboundTenant($existingTenant);

                    if ($resolvedWorkspaceId === (int) $this->workspace->getKey()) {
                        $existingTenant->forceFill(['workspace_id' => $resolvedWorkspaceId])->save();
                    }
                }

                if ((int) $existingTenant->workspace_id !== (int) $this->workspace->getKey()) {
                    abort(404);
                }

                $existingTenant->forceFill([
                    'name' => $tenantName,
                    'environment' => $environment,
                    'domain' => $primaryDomain,
                    'status' => $existingTenant->status === Tenant::STATUS_DRAFT ? Tenant::STATUS_ONBOARDING : $existingTenant->status,
                    'metadata' => array_merge(is_array($existingTenant->metadata) ? $existingTenant->metadata : [], array_filter([
                        'notes' => $notes,
                    ], static fn ($value): bool => $value !== null)),
                ])->save();

                $tenant = $existingTenant;
            } else {
                try {
                    $tenant = Tenant::query()->create([
                        'workspace_id' => (int) $this->workspace->getKey(),
                        'name' => $tenantName,
                        'tenant_id' => $entraTenantId,
                        'domain' => $primaryDomain,
                        'environment' => $environment,
                        'status' => Tenant::STATUS_ONBOARDING,
                        'metadata' => array_filter([
                            'notes' => $notes,
                        ], static fn ($value): bool => $value !== null),
                    ]);
                } catch (QueryException $exception) {
                    // Race-safe global uniqueness: if another workspace created the tenant_id first,
                    // treat it as deny-as-not-found.
                    $existingTenant = Tenant::query()
                        ->withTrashed()
                        ->where('tenant_id', $entraTenantId)
                        ->first();

                    if ($existingTenant instanceof Tenant && (int) $existingTenant->workspace_id !== (int) $this->workspace->getKey()) {
                        abort(404);
                    }

                    if ($existingTenant instanceof Tenant && (int) $existingTenant->workspace_id === (int) $this->workspace->getKey()) {
                        $tenant = $existingTenant;
                    } else {
                        throw $exception;
                    }
                }
            }

            $membershipManager->addMember(
                tenant: $tenant,
                actor: $user,
                member: $user,
                role: 'owner',
                source: 'manual',
            );

            $ownerCount = TenantMembership::query()
                ->where('tenant_id', $tenant->getKey())
                ->where('role', 'owner')
                ->count();

            if ($ownerCount === 0) {
                throw new RuntimeException('Tenant must have at least one owner.');
            }

            $session = TenantOnboardingSession::query()
                ->where('workspace_id', (int) $this->workspace->getKey())
                ->where('entra_tenant_id', $entraTenantId)
                ->whereNull('completed_at')
                ->first();

            $sessionWasCreated = false;

            if (! $session instanceof TenantOnboardingSession) {
                $session = new TenantOnboardingSession;
                $session->workspace_id = (int) $this->workspace->getKey();
                $session->entra_tenant_id = $entraTenantId;
                $session->tenant_id = (int) $tenant->getKey();
                $session->started_by_user_id = (int) $user->getKey();
                $sessionWasCreated = true;
            }

            $session->entra_tenant_id = $entraTenantId;
            $session->tenant_id = (int) $tenant->getKey();
            $session->current_step = 'identify';
            $session->state = array_merge($session->state ?? [], [
                'entra_tenant_id' => $entraTenantId,
                'tenant_name' => $tenantName,
                'environment' => $environment,
                'primary_domain' => $primaryDomain,
                'notes' => $notes,
            ]);
            $session->updated_by_user_id = (int) $user->getKey();
            $session->save();

            $this->selectedProviderConnectionId ??= $this->resolveDefaultProviderConnectionId($tenant);

            if ($this->selectedProviderConnectionId !== null) {
                $session->state = array_merge($session->state ?? [], [
                    'provider_connection_id' => (int) $this->selectedProviderConnectionId,
                ]);
                $session->save();
            }

            $auditLogger->log(
                workspace: $this->workspace,
                action: ($sessionWasCreated
                    ? AuditActionId::ManagedTenantOnboardingStart
                    : AuditActionId::ManagedTenantOnboardingResume
                )->value,
                context: [
                    'metadata' => [
                        'workspace_id' => (int) $this->workspace->getKey(),
                        'tenant_db_id' => (int) $tenant->getKey(),
                        'entra_tenant_id' => $entraTenantId,
                        'tenant_name' => $tenantName,
                        'onboarding_session_id' => (int) $session->getKey(),
                        'current_step' => (string) $session->current_step,
                    ],
                ],
                actor: $user,
                status: 'success',
                resourceType: 'tenant',
                resourceId: (string) $tenant->getKey(),
            );

            $this->managedTenant = $tenant;
            $this->onboardingSession = $session;
        });

        Notification::make()
            ->title('Managed tenant identified')
            ->success()
            ->send();
    }

    public function selectProviderConnection(int $providerConnectionId): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_VIEW);

        if (! $this->managedTenant instanceof Tenant) {
            abort(404);
        }

        $connection = ProviderConnection::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', $this->managedTenant->getKey())
            ->whereKey($providerConnectionId)
            ->first();

        if (! $connection instanceof ProviderConnection) {
            abort(404);
        }

        $previousProviderConnectionId = $this->selectedProviderConnectionId;

        $this->selectedProviderConnectionId = (int) $connection->getKey();

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $this->onboardingSession->state = $this->resetDependentOnboardingStateOnConnectionChange(
                state: array_merge($this->onboardingSession->state ?? [], [
                    'provider_connection_id' => (int) $connection->getKey(),
                ]),
                previousProviderConnectionId: $previousProviderConnectionId,
                newProviderConnectionId: (int) $connection->getKey(),
            );
            $this->onboardingSession->current_step = 'connection';
            $this->onboardingSession->updated_by_user_id = (int) $user->getKey();
            $this->onboardingSession->save();
        }

        Notification::make()
            ->title('Provider connection selected')
            ->success()
            ->send();
    }

    /**
     * @param  array{display_name: string, client_id: string, client_secret: string, is_default?: bool}  $data
     */
    public function createProviderConnection(array $data): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE);

        if (! $this->managedTenant instanceof Tenant) {
            abort(404);
        }

        $tenant = $this->managedTenant->fresh();

        if (! $tenant instanceof Tenant) {
            abort(404);
        }

        if ((int) $tenant->workspace_id !== (int) $this->workspace->getKey()) {
            abort(404);
        }

        $displayName = trim((string) ($data['display_name'] ?? ''));
        $clientId = (string) ($data['client_id'] ?? '');
        $clientSecret = (string) ($data['client_secret'] ?? '');
        $makeDefault = (bool) ($data['is_default'] ?? false);

        if ($displayName === '') {
            abort(422);
        }

        /** @var ProviderConnection $connection */
        $connection = DB::transaction(function () use ($tenant, $displayName, $clientId, $clientSecret, $makeDefault): ProviderConnection {
            $connection = ProviderConnection::query()->updateOrCreate(
                [
                    'tenant_id' => (int) $tenant->getKey(),
                    'provider' => 'microsoft',
                    'entra_tenant_id' => (string) $tenant->tenant_id,
                ],
                [
                    'workspace_id' => (int) $tenant->workspace_id,
                    'display_name' => $displayName,
                ],
            );

            app(CredentialManager::class)->upsertClientSecretCredential(
                connection: $connection,
                clientId: $clientId,
                clientSecret: $clientSecret,
            );

            if ($makeDefault) {
                $connection->makeDefault();
            }

            return $connection;
        });

        $this->selectedProviderConnectionId = (int) $connection->getKey();

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $previousProviderConnectionId = $this->onboardingSession->state['provider_connection_id'] ?? null;
            $previousProviderConnectionId = is_int($previousProviderConnectionId)
                ? $previousProviderConnectionId
                : (is_numeric($previousProviderConnectionId) ? (int) $previousProviderConnectionId : null);

            $this->onboardingSession->state = $this->resetDependentOnboardingStateOnConnectionChange(
                state: array_merge($this->onboardingSession->state ?? [], [
                    'provider_connection_id' => (int) $connection->getKey(),
                ]),
                previousProviderConnectionId: $previousProviderConnectionId,
                newProviderConnectionId: (int) $connection->getKey(),
            );
            $this->onboardingSession->current_step = 'connection';
            $this->onboardingSession->updated_by_user_id = (int) $user->getKey();
            $this->onboardingSession->save();
        }

        Notification::make()
            ->title('Provider connection created')
            ->success()
            ->send();
    }

    public function startVerification(): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START);

        if (! $this->managedTenant instanceof Tenant) {
            Notification::make()
                ->title('Identify a managed tenant first')
                ->warning()
                ->send();

            return;
        }

        $tenant = $this->managedTenant->fresh();

        if (! $tenant instanceof Tenant) {
            abort(404);
        }

        $connection = $this->resolveSelectedProviderConnection($tenant);

        if (! $connection instanceof ProviderConnection) {
            Notification::make()
                ->title('No provider connection selected')
                ->body('Create or select a provider connection first.')
                ->warning()
                ->send();

            return;
        }

        $result = app(ProviderOperationStartGate::class)->start(
            tenant: $tenant,
            connection: $connection,
            operationType: 'provider.connection.check',
            dispatcher: function (OperationRun $run) use ($tenant, $user, $connection): void {
                ProviderConnectionHealthCheckJob::dispatch(
                    tenantId: (int) $tenant->getKey(),
                    userId: (int) $user->getKey(),
                    providerConnectionId: (int) $connection->getKey(),
                    operationRun: $run,
                );
            },
            initiator: $user,
            extraContext: [
                'wizard' => [
                    'flow' => 'managed_tenant_onboarding',
                    'step' => 'verification',
                ],
            ],
        );

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $this->onboardingSession->state = array_merge($this->onboardingSession->state ?? [], [
                'provider_connection_id' => (int) $connection->getKey(),
                'verification_operation_run_id' => (int) $result->run->getKey(),
                'connection_recently_updated' => false,
            ]);
            $this->onboardingSession->current_step = 'verify';
            $this->onboardingSession->updated_by_user_id = (int) $user->getKey();
            $this->onboardingSession->save();
        }

        $auditStatus = match ($result->status) {
            'started' => 'success',
            'deduped' => 'deduped',
            'scope_busy' => 'blocked',
            default => 'success',
        };

        app(WorkspaceAuditLogger::class)->log(
            workspace: $this->workspace,
            action: AuditActionId::ManagedTenantOnboardingVerificationStart->value,
            context: [
                'metadata' => [
                    'workspace_id' => (int) $this->workspace->getKey(),
                    'tenant_db_id' => (int) $tenant->getKey(),
                    'provider_connection_id' => (int) $connection->getKey(),
                    'operation_run_id' => (int) $result->run->getKey(),
                    'result' => (string) $result->status,
                ],
            ],
            actor: $user,
            status: $auditStatus,
            resourceType: 'operation_run',
            resourceId: (string) $result->run->getKey(),
        );

        if ($result->status === 'scope_busy') {
            OpsUxBrowserEvents::dispatchRunEnqueued($this);

            Notification::make()
                ->title('Another operation is already running')
                ->body('Please wait for the active run to finish.')
                ->warning()
                ->actions([
                    Action::make('view_run')
                        ->label('View run')
                        ->url($this->tenantlessOperationRunUrl((int) $result->run->getKey())),
                ])
                ->send();

            return;
        }

        if ($result->status === 'blocked') {
            $reasonCode = is_string($result->run->context['reason_code'] ?? null)
                ? (string) $result->run->context['reason_code']
                : 'unknown_error';

            $actions = [
                Action::make('view_run')
                    ->label('View run')
                    ->url($this->tenantlessOperationRunUrl((int) $result->run->getKey())),
            ];

            $nextSteps = $result->run->context['next_steps'] ?? [];
            $nextSteps = is_array($nextSteps) ? $nextSteps : [];

            foreach ($nextSteps as $index => $step) {
                if (! is_array($step)) {
                    continue;
                }

                $label = is_string($step['label'] ?? null) ? trim((string) $step['label']) : '';
                $url = is_string($step['url'] ?? null) ? trim((string) $step['url']) : '';

                if ($label === '' || $url === '') {
                    continue;
                }

                $actions[] = Action::make('next_step_'.$index)
                    ->label($label)
                    ->url($url);

                break;
            }

            Notification::make()
                ->title('Verification blocked')
                ->body("Blocked by provider configuration ({$reasonCode}).")
                ->warning()
                ->actions($actions)
                ->send();

            return;
        }

        OpsUxBrowserEvents::dispatchRunEnqueued($this);

        if ($result->status === 'deduped') {
            OperationUxPresenter::alreadyQueuedToast((string) $result->run->type)
                ->actions([
                    Action::make('view_run')
                        ->label('View run')
                        ->url($this->tenantlessOperationRunUrl((int) $result->run->getKey())),
                ])
                ->send();

            return;
        }

        OperationUxPresenter::queuedToast((string) $result->run->type)
            ->actions([
                Action::make('view_run')
                    ->label('View run')
                    ->url($this->tenantlessOperationRunUrl((int) $result->run->getKey())),
            ])
            ->send();
    }

    public function refreshVerificationStatus(): void
    {
        if ($this->managedTenant instanceof Tenant) {
            $this->managedTenant->refresh();
        }

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $this->onboardingSession->refresh();
        }

        Notification::make()
            ->title('Verification refreshed')
            ->success()
            ->send();
    }

    /**
     * @param  array<int, mixed>  $operationTypes
     */
    public function startBootstrap(array $operationTypes): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMember($user);

        if (! $this->managedTenant instanceof Tenant) {
            abort(404);
        }

        $tenant = $this->managedTenant->fresh();

        if (! $tenant instanceof Tenant) {
            abort(404);
        }

        if (! $this->verificationHasSucceeded()) {
            Notification::make()
                ->title('Verification required')
                ->body('Run verification successfully before starting bootstrap actions.')
                ->warning()
                ->send();

            return;
        }

        $connection = $this->resolveSelectedProviderConnection($tenant);

        if (! $connection instanceof ProviderConnection) {
            Notification::make()
                ->title('No provider connection selected')
                ->warning()
                ->send();

            return;
        }

        $registry = app(ProviderOperationRegistry::class);
        $types = array_values(array_unique(array_filter($operationTypes, static fn ($v): bool => is_string($v) && trim($v) !== '')));

        $types = array_values(array_filter(
            $types,
            static fn (string $type): bool => $type !== 'provider.connection.check' && $registry->isAllowed($type),
        ));

        foreach ($types as $operationType) {
            $capability = $this->resolveBootstrapCapability($operationType);

            if ($capability === null) {
                abort(422);
            }

            if (! $user->can($capability, $this->workspace)) {
                abort(403);
            }
        }

        if (empty($types)) {
            Notification::make()
                ->title('No bootstrap actions selected')
                ->warning()
                ->send();

            return;
        }

        /** @var array{status: 'started', runs: array<string, int>, created: array<string, bool>}|array{status: 'scope_busy', run: OperationRun} $result */
        $result = DB::transaction(function () use ($tenant, $connection, $types, $registry, $user): array {
            $lockedConnection = ProviderConnection::query()
                ->whereKey($connection->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            $activeRun = OperationRun::query()
                ->where('tenant_id', $tenant->getKey())
                ->active()
                ->where('context->provider_connection_id', (int) $lockedConnection->getKey())
                ->orderByDesc('id')
                ->first();

            if ($activeRun instanceof OperationRun) {
                return [
                    'status' => 'scope_busy',
                    'run' => $activeRun,
                ];
            }

            $runsService = app(OperationRunService::class);

            $bootstrapRuns = [];
            $bootstrapCreated = [];

            foreach ($types as $operationType) {
                $definition = $registry->get($operationType);

                $context = [
                    'wizard' => [
                        'flow' => 'managed_tenant_onboarding',
                        'step' => 'bootstrap',
                    ],
                    'provider' => $lockedConnection->provider,
                    'module' => $definition['module'],
                    'provider_connection_id' => (int) $lockedConnection->getKey(),
                    'target_scope' => [
                        'entra_tenant_id' => $lockedConnection->entra_tenant_id,
                    ],
                ];

                $run = $runsService->ensureRunWithIdentity(
                    tenant: $tenant,
                    type: $operationType,
                    identityInputs: [
                        'provider_connection_id' => (int) $lockedConnection->getKey(),
                    ],
                    context: $context,
                    initiator: $user,
                );

                if ($run->wasRecentlyCreated) {
                    $this->dispatchBootstrapJob(
                        operationType: $operationType,
                        tenantId: (int) $tenant->getKey(),
                        userId: (int) $user->getKey(),
                        providerConnectionId: (int) $lockedConnection->getKey(),
                        run: $run,
                    );
                }

                $bootstrapRuns[$operationType] = (int) $run->getKey();
                $bootstrapCreated[$operationType] = (bool) $run->wasRecentlyCreated;
            }

            return [
                'status' => 'started',
                'runs' => $bootstrapRuns,
                'created' => $bootstrapCreated,
            ];
        });

        if ($result['status'] === 'scope_busy') {
            OpsUxBrowserEvents::dispatchRunEnqueued($this);

            Notification::make()
                ->title('Another operation is already running')
                ->body('Please wait for the active run to finish.')
                ->warning()
                ->actions([
                    Action::make('view_run')
                        ->label('View run')
                        ->url($this->tenantlessOperationRunUrl((int) $result['run']->getKey())),
                ])
                ->send();

            return;
        }

        $bootstrapRuns = $result['runs'];

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $state = $this->onboardingSession->state ?? [];

            $existing = $state['bootstrap_operation_runs'] ?? [];
            $existing = is_array($existing) ? $existing : [];

            $state['bootstrap_operation_runs'] = array_merge($existing, $bootstrapRuns);
            $state['bootstrap_operation_types'] = $types;

            $this->onboardingSession->state = $state;
            $this->onboardingSession->current_step = 'bootstrap';
            $this->onboardingSession->updated_by_user_id = (int) $user->getKey();
            $this->onboardingSession->save();
        }

        OpsUxBrowserEvents::dispatchRunEnqueued($this);

        foreach ($types as $operationType) {
            $runId = (int) ($bootstrapRuns[$operationType] ?? 0);
            $runUrl = $runId > 0 ? $this->tenantlessOperationRunUrl($runId) : null;
            $wasCreated = (bool) ($result['created'][$operationType] ?? false);

            $toast = $wasCreated
                ? OperationUxPresenter::queuedToast($operationType)
                : OperationUxPresenter::alreadyQueuedToast($operationType);

            if ($runUrl !== null) {
                $toast->actions([
                    Action::make('view_run')
                        ->label('View run')
                        ->url($runUrl),
                ]);
            }

            $toast->send();
        }
    }

    private function dispatchBootstrapJob(
        string $operationType,
        int $tenantId,
        int $userId,
        int $providerConnectionId,
        OperationRun $run,
    ): void {
        match ($operationType) {
            'inventory_sync' => ProviderInventorySyncJob::dispatch(
                tenantId: $tenantId,
                userId: $userId,
                providerConnectionId: $providerConnectionId,
                operationRun: $run,
            ),
            'compliance.snapshot' => ProviderComplianceSnapshotJob::dispatch(
                tenantId: $tenantId,
                userId: $userId,
                providerConnectionId: $providerConnectionId,
                operationRun: $run,
            ),
            default => throw new RuntimeException("Unsupported bootstrap operation type: {$operationType}"),
        };
    }

    private function resolveBootstrapCapability(string $operationType): ?string
    {
        return match ($operationType) {
            'inventory_sync' => Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC,
            'compliance.snapshot' => Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_POLICY_SYNC,
            default => null,
        };
    }

    private function canStartAnyBootstrap(): bool
    {
        return $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC)
            || $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_POLICY_SYNC)
            || $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP);
    }

    private function currentUserCan(string $capability): bool
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            return false;
        }

        return $user->can($capability, $this->workspace);
    }

    private function tenantlessOperationRunUrl(int $runId): string
    {
        return OperationRunLinks::tenantlessView($runId);
    }

    public function verificationSucceeded(): bool
    {
        return $this->verificationHasSucceeded();
    }

    private function canCompleteOnboarding(): bool
    {
        if (! $this->managedTenant instanceof Tenant) {
            return false;
        }

        if (! $this->resolveSelectedProviderConnection($this->managedTenant)) {
            return false;
        }

        if ($this->verificationHasSucceeded()) {
            return true;
        }

        if (! (bool) ($this->data['override_blocked'] ?? false)) {
            return false;
        }

        if ($this->verificationStatus() !== 'blocked') {
            return false;
        }

        return trim((string) ($this->data['override_reason'] ?? '')) !== '';
    }

    public function completeOnboarding(): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_ACTIVATE);

        if (! $this->managedTenant instanceof Tenant) {
            abort(404);
        }

        if (! $this->onboardingSession instanceof TenantOnboardingSession) {
            abort(404);
        }

        $run = $this->verificationRun();
        $verificationSucceeded = $run instanceof OperationRun
            && $run->status === OperationRunStatus::Completed->value
            && $run->outcome === OperationRunOutcome::Succeeded->value;

        $verificationBlocked = $run instanceof OperationRun
            && $run->status === OperationRunStatus::Completed->value
            && $run->outcome === OperationRunOutcome::Failed->value;

        if (! $verificationSucceeded) {
            $overrideBlocked = (bool) ($this->data['override_blocked'] ?? false);

            if (! $overrideBlocked) {
                Notification::make()
                    ->title('Verification required')
                    ->body('Complete verification successfully before finishing onboarding.')
                    ->warning()
                    ->send();

                return;
            }

            if (! $verificationBlocked) {
                throw ValidationException::withMessages([
                    'data.override_blocked' => 'Verification override is only allowed when verification is blocked.',
                ]);
            }

            $overrideReason = trim((string) ($this->data['override_reason'] ?? ''));

            if ($overrideReason === '') {
                throw ValidationException::withMessages([
                    'data.override_reason' => 'Override reason is required.',
                ]);
            }
        }

        $tenant = $this->managedTenant->fresh();

        if (! $tenant instanceof Tenant) {
            abort(404);
        }

        $connection = $this->resolveSelectedProviderConnection($tenant);

        if (! $connection instanceof ProviderConnection) {
            Notification::make()
                ->title('Provider connection required')
                ->body('Create or select a provider connection before finishing onboarding.')
                ->warning()
                ->send();

            return;
        }

        $overrideBlocked = (bool) ($this->data['override_blocked'] ?? false);
        $overrideReason = trim((string) ($this->data['override_reason'] ?? ''));

        DB::transaction(function () use ($tenant, $user): void {
            $tenant->update(['status' => Tenant::STATUS_ACTIVE]);

            $this->onboardingSession->forceFill([
                'completed_at' => now(),
                'current_step' => 'complete',
                'updated_by_user_id' => (int) $user->getKey(),
            ])->save();
        });

        app(WorkspaceAuditLogger::class)->log(
            workspace: $this->workspace,
            action: AuditActionId::ManagedTenantOnboardingActivation->value,
            context: [
                'metadata' => [
                    'workspace_id' => (int) $this->workspace->getKey(),
                    'tenant_db_id' => (int) $tenant->getKey(),
                    'provider_connection_id' => (int) $connection->getKey(),
                    'verification_operation_run_id' => $run instanceof OperationRun ? (int) $run->getKey() : null,
                    'verification_succeeded' => $verificationSucceeded,
                    'override_blocked' => $overrideBlocked,
                    'override_reason' => $overrideBlocked ? $overrideReason : null,
                ],
            ],
            actor: $user,
            status: $overrideBlocked ? 'override' : 'success',
            resourceType: 'tenant',
            resourceId: (string) $tenant->getKey(),
        );

        $this->redirect(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant));
    }

    private function verificationRun(): ?OperationRun
    {
        if (! $this->managedTenant instanceof Tenant) {
            return null;
        }

        if (! $this->onboardingSession instanceof TenantOnboardingSession) {
            return null;
        }

        $runId = $this->onboardingSession->state['verification_operation_run_id'] ?? null;

        if (! is_int($runId)) {
            return null;
        }

        return OperationRun::query()
            ->where('tenant_id', (int) $this->managedTenant->getKey())
            ->whereKey($runId)
            ->first();
    }

    private function verificationHasSucceeded(): bool
    {
        $run = $this->verificationRun();

        if (! $run instanceof OperationRun) {
            return false;
        }

        return $run->status === OperationRunStatus::Completed->value
            && $run->outcome === OperationRunOutcome::Succeeded->value
            && $this->verificationRunMatchesSelectedConnection($run);
    }

    private function verificationRunIsStaleForSelectedConnection(): bool
    {
        $run = $this->verificationRun();

        if (! $run instanceof OperationRun) {
            return false;
        }

        return ! $this->verificationRunMatchesSelectedConnection($run);
    }

    private function verificationRunMatchesSelectedConnection(OperationRun $run): bool
    {
        $selectedProviderConnectionId = $this->selectedProviderConnectionId;

        if ($selectedProviderConnectionId === null && $this->onboardingSession instanceof TenantOnboardingSession) {
            $candidate = $this->onboardingSession->state['provider_connection_id'] ?? null;
            $selectedProviderConnectionId = is_int($candidate)
                ? $candidate
                : (is_numeric($candidate) ? (int) $candidate : null);
        }

        if ($selectedProviderConnectionId === null) {
            return false;
        }

        $context = is_array($run->context ?? null) ? $run->context : [];
        $runProviderConnectionId = $context['provider_connection_id'] ?? null;
        $runProviderConnectionId = is_int($runProviderConnectionId)
            ? $runProviderConnectionId
            : (is_numeric($runProviderConnectionId) ? (int) $runProviderConnectionId : null);

        if ($runProviderConnectionId === null) {
            return false;
        }

        return $runProviderConnectionId === $selectedProviderConnectionId;
    }

    /**
     * @param  array<string, mixed>  $state
     * @return array<string, mixed>
     */
    private function resetDependentOnboardingStateOnConnectionChange(array $state, ?int $previousProviderConnectionId, int $newProviderConnectionId): array
    {
        if ($previousProviderConnectionId === null) {
            return $state;
        }

        if ($previousProviderConnectionId === $newProviderConnectionId) {
            return $state;
        }

        unset(
            $state['verification_operation_run_id'],
            $state['bootstrap_operation_runs'],
            $state['bootstrap_operation_types'],
        );

        $state['connection_recently_updated'] = true;

        return $state;
    }

    private function connectionRecentlyUpdated(): bool
    {
        if (! $this->onboardingSession instanceof TenantOnboardingSession) {
            return false;
        }

        return (bool) ($this->onboardingSession->state['connection_recently_updated'] ?? false);
    }

    /**
     * @return array{display_name: string, entra_tenant_id: string, client_id: string}
     */
    private function inlineEditSelectedConnectionFill(int $providerConnectionId): array
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE);

        if (! $this->managedTenant instanceof Tenant) {
            abort(404);
        }

        $connection = ProviderConnection::query()
            ->with('credential')
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', (int) $this->managedTenant->getKey())
            ->whereKey($providerConnectionId)
            ->first();

        if (! $connection instanceof ProviderConnection) {
            abort(404);
        }

        $payload = $connection->credential?->payload;
        $payload = is_array($payload) ? $payload : [];

        return [
            'display_name' => (string) $connection->display_name,
            'entra_tenant_id' => (string) $connection->entra_tenant_id,
            'client_id' => (string) ($payload['client_id'] ?? ''),
        ];
    }

    /**
     * @param  array{display_name?: mixed, client_id?: mixed}  $data
     */
    public function updateSelectedProviderConnectionInline(int $providerConnectionId, array $data): void
    {
        $user = auth()->user();

        if (! $user instanceof User) {
            abort(403);
        }

        $this->authorizeWorkspaceMutation($user, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE);

        if (! $this->managedTenant instanceof Tenant) {
            abort(404);
        }

        $connection = ProviderConnection::query()
            ->with('credential')
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', (int) $this->managedTenant->getKey())
            ->whereKey($providerConnectionId)
            ->first();

        if (! $connection instanceof ProviderConnection) {
            abort(404);
        }

        $displayName = trim((string) ($data['display_name'] ?? ''));
        $clientId = trim((string) ($data['client_id'] ?? ''));

        if ($displayName === '') {
            throw ValidationException::withMessages([
                'display_name' => 'Connection name is required.',
            ]);
        }

        if ($clientId === '') {
            throw ValidationException::withMessages([
                'client_id' => 'App (client) ID is required.',
            ]);
        }

        $existingPayload = $connection->credential?->payload;
        $existingPayload = is_array($existingPayload) ? $existingPayload : [];
        $existingClientId = trim((string) ($existingPayload['client_id'] ?? ''));

        $changedFields = [];

        if ($displayName !== (string) $connection->display_name) {
            $changedFields[] = 'display_name';
        }

        if ($clientId !== $existingClientId) {
            $changedFields[] = 'client_id';
        }

        DB::transaction(function () use ($connection, $displayName, $clientId): void {
            $connection->forceFill([
                'display_name' => $displayName,
            ])->save();

            app(CredentialManager::class)->updateClientIdPreservingSecret(
                connection: $connection,
                clientId: $clientId,
            );
        });

        if ($changedFields !== []) {
            app(AuditLogger::class)->log(
                tenant: $this->managedTenant,
                action: 'provider_connection.updated',
                context: [
                    'metadata' => [
                        'workspace_id' => (int) $this->workspace->getKey(),
                        'provider_connection_id' => (int) $connection->getKey(),
                        'provider' => (string) $connection->provider,
                        'entra_tenant_id' => (string) $connection->entra_tenant_id,
                        'fields' => $changedFields,
                        'source' => 'managed_tenant_onboarding_wizard.inline_edit',
                    ],
                ],
                actorId: (int) $user->getKey(),
                actorEmail: (string) $user->email,
                actorName: (string) $user->name,
                resourceType: 'provider_connection',
                resourceId: (string) $connection->getKey(),
                status: 'success',
            );
        }

        if ($this->onboardingSession instanceof TenantOnboardingSession) {
            $state = is_array($this->onboardingSession->state) ? $this->onboardingSession->state : [];

            unset(
                $state['verification_operation_run_id'],
                $state['bootstrap_operation_runs'],
                $state['bootstrap_operation_types'],
            );

            $state['connection_recently_updated'] = true;

            $this->onboardingSession->state = array_merge($state, [
                'provider_connection_id' => (int) $connection->getKey(),
            ]);
            $this->onboardingSession->updated_by_user_id = (int) $user->getKey();
            $this->onboardingSession->save();
        }

        Notification::make()
            ->title('Connection updated')
            ->success()
            ->send();

        $this->initializeWizardData();
    }

    /**
     * @return array<string, string>
     */
    private function bootstrapOperationOptions(): array
    {
        $registry = app(ProviderOperationRegistry::class);

        return collect($registry->all())
            ->reject(fn (array $definition, string $type): bool => $type === 'provider.connection.check')
            ->mapWithKeys(fn (array $definition, string $type): array => [$type => (string) ($definition['label'] ?? $type)])
            ->all();
    }

    private function resolveDefaultProviderConnectionId(Tenant $tenant): ?int
    {
        $id = ProviderConnection::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', (int) $tenant->getKey())
            ->where('is_default', true)
            ->orderByDesc('id')
            ->value('id');

        if (is_int($id)) {
            return $id;
        }

        $fallback = ProviderConnection::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', (int) $tenant->getKey())
            ->orderByDesc('id')
            ->value('id');

        return is_int($fallback) ? $fallback : null;
    }

    private function resolveSelectedProviderConnection(Tenant $tenant): ?ProviderConnection
    {
        $providerConnectionId = $this->selectedProviderConnectionId;

        if (! is_int($providerConnectionId) && $this->onboardingSession instanceof TenantOnboardingSession) {
            $candidate = $this->onboardingSession->state['provider_connection_id'] ?? null;
            $providerConnectionId = is_int($candidate) ? $candidate : null;
        }

        if (! is_int($providerConnectionId)) {
            $providerConnectionId = $this->resolveDefaultProviderConnectionId($tenant);
        }

        if (! is_int($providerConnectionId)) {
            return null;
        }

        return ProviderConnection::query()
            ->where('workspace_id', (int) $this->workspace->getKey())
            ->where('tenant_id', (int) $tenant->getKey())
            ->whereKey($providerConnectionId)
            ->first();
    }
}
