<?php

declare(strict_types=1);

namespace App\Services\Tenants;

use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\User;
use App\Services\Onboarding\OnboardingLifecycleService;
use App\Support\Audit\AuditActionId;
use App\Support\Tenants\TenantActionContext;
use App\Support\Tenants\TenantActionDescriptor;
use App\Support\Tenants\TenantActionFamily;
use App\Support\Tenants\TenantActionSurface;
use App\Support\Tenants\TenantInteractionLane;
use App\Support\Tenants\TenantOperabilityOutcome;
use App\Support\Tenants\TenantOperabilityQuestion;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Support\Facades\Gate;

class TenantActionPolicySurface
{
    public function __construct(
        private readonly TenantOperabilityService $tenantOperabilityService,
        private readonly OnboardingLifecycleService $onboardingLifecycleService,
        private readonly WorkspaceContext $workspaceContext,
    ) {}

    public function buildContext(ManagedEnvironment $tenant, TenantActionSurface $surface, ?User $user = null): TenantActionContext
    {
        $user ??= auth()->user();
        $draft = $user instanceof User ? $this->relatedOnboardingDraft($tenant, $user) : null;
        $lifecycle = $this->tenantOperabilityService->lifecycleFor($tenant);
        $lane = $surface->isOnboardingSurface()
            ? TenantInteractionLane::OnboardingWorkflow
            : TenantInteractionLane::AdministrativeManagement;
        $workspaceId = request() !== null
            ? $this->workspaceContext->currentWorkspaceId(request())
            : null;
        $resumeOutcome = $draft instanceof ManagedEnvironmentOnboardingSession
            ? $this->tenantOperabilityService->outcomeFor(
                tenant: $tenant,
                question: TenantOperabilityQuestion::ResumeOnboardingEligibility,
                actor: $user instanceof User ? $user : null,
                workspaceId: $workspaceId,
                lane: $lane,
                onboardingDraft: $draft,
            )
            : null;

        return new TenantActionContext(
            tenant: $tenant,
            lifecycle: $lifecycle,
            surface: $surface,
            actor: $user instanceof User ? $user : null,
            workspaceId: $workspaceId,
            lane: $lane,
            relatedOnboardingDraft: $draft,
            relatedOnboardingIsResumable: $resumeOutcome?->allowed ?? false,
            hasRelatedOnboardingDraft: $draft instanceof ManagedEnvironmentOnboardingSession,
            isArchived: $lifecycle->canRestore(),
        );
    }

    /**
     * @return list<TenantActionDescriptor>
     */
    public function catalogForTenant(ManagedEnvironment $tenant, TenantActionSurface $surface, ?User $user = null): array
    {
        $context = $this->buildContext($tenant, $surface, $user);
        $actions = [
            $this->viewAction(),
        ];

        $primaryAction = $this->primaryActionForContext($context);

        if ($primaryAction instanceof TenantActionDescriptor) {
            $actions[] = $primaryAction;
        }

        $secondaryActions = [
            $this->secondaryRelatedOnboardingActionForContext($context, $primaryAction),
            $this->secondaryLifecycleActionForContext($context, $primaryAction),
        ];

        foreach ($secondaryActions as $secondaryAction) {
            if ($secondaryAction instanceof TenantActionDescriptor) {
                $actions[] = $secondaryAction;
            }
        }

        return array_values(array_filter($actions, static fn (mixed $action): bool => $action instanceof TenantActionDescriptor && $action->visible));
    }

    public function lifecycleActionForTenant(ManagedEnvironment $tenant): ?TenantActionDescriptor
    {
        return $this->lifecycleActionForContext($this->buildContext($tenant, TenantActionSurface::ContextMenu));
    }

    public function relatedOnboardingActionForTenant(ManagedEnvironment $tenant, TenantActionSurface $surface, ?User $user = null): ?TenantActionDescriptor
    {
        return $this->relatedOnboardingActionForContext($this->buildContext($tenant, $surface, $user));
    }

    public function onboardingEntryDescriptor(int $resumableDraftCount): TenantActionDescriptor
    {
        return match (true) {
            $resumableDraftCount === 1 => new TenantActionDescriptor(
                key: 'resume_onboarding',
                family: TenantActionFamily::OnboardingWorkflow,
                label: 'Resume onboarding',
                icon: 'heroicon-m-arrow-path',
                group: 'primary',
            ),
            $resumableDraftCount > 1 => new TenantActionDescriptor(
                key: 'choose_onboarding_draft',
                family: TenantActionFamily::OnboardingWorkflow,
                label: 'Choose onboarding draft',
                icon: 'heroicon-m-queue-list',
                group: 'primary',
            ),
            default => new TenantActionDescriptor(
                key: 'add_tenant',
                family: TenantActionFamily::OnboardingWorkflow,
                label: 'Add environment',
                icon: 'heroicon-m-plus',
                group: 'primary',
            ),
        };
    }

    public function relatedOnboardingDraft(ManagedEnvironment $tenant, ?User $user = null): ?ManagedEnvironmentOnboardingSession
    {
        $user ??= auth()->user();

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

        return ManagedEnvironmentOnboardingSession::query()
            ->where('workspace_id', (int) $tenant->workspace_id)
            ->where('managed_environment_id', (int) $tenant->getKey())
            ->orderByDesc('updated_at')
            ->get()
            ->first(fn (ManagedEnvironmentOnboardingSession $draft): bool => Gate::forUser($user)->allows('view', $draft));
    }

    private function viewAction(): TenantActionDescriptor
    {
        return new TenantActionDescriptor(
            key: 'view',
            family: TenantActionFamily::Neutral,
            label: 'View',
            icon: 'heroicon-o-eye',
            group: 'inspect',
        );
    }

    private function primaryActionForContext(TenantActionContext $context): ?TenantActionDescriptor
    {
        if ($context->relatedOnboardingIsResumable && $context->lifecycle->canResumeOnboarding()) {
            return $this->relatedOnboardingActionForContext($context, 'primary');
        }

        return $this->lifecycleActionForContext($context, 'primary');
    }

    private function secondaryLifecycleActionForContext(
        TenantActionContext $context,
        ?TenantActionDescriptor $primaryAction,
    ): ?TenantActionDescriptor {
        $action = $this->lifecycleActionForContext($context);

        if (! $action instanceof TenantActionDescriptor) {
            return null;
        }

        if ($primaryAction instanceof TenantActionDescriptor && $primaryAction->key === $action->key) {
            return null;
        }

        return $action;
    }

    private function secondaryRelatedOnboardingActionForContext(
        TenantActionContext $context,
        ?TenantActionDescriptor $primaryAction,
    ): ?TenantActionDescriptor {
        $action = $this->relatedOnboardingActionForContext($context);

        if (! $action instanceof TenantActionDescriptor) {
            return null;
        }

        if ($primaryAction instanceof TenantActionDescriptor && $primaryAction->key === $action->key) {
            return null;
        }

        return $action;
    }

    private function lifecycleActionForContext(
        TenantActionContext $context,
        string $group = 'overflow',
    ): ?TenantActionDescriptor {
        if (! $context->isGenericTenantManagementSurface()) {
            return null;
        }

        $restoreOutcome = $this->tenantOperabilityService->outcomeFor(
            tenant: $context->tenant,
            question: TenantOperabilityQuestion::RestoreEligibility,
            actor: $context->actor,
            workspaceId: $context->workspaceId,
            lane: $context->lane,
        );

        if ($this->shouldExposeAction($restoreOutcome)) {
            return new TenantActionDescriptor(
                key: 'restore',
                family: TenantActionFamily::LifecycleManagement,
                label: 'Restore',
                icon: 'heroicon-o-arrow-uturn-left',
                destructive: true,
                requiresConfirmation: true,
                auditActionId: AuditActionId::TenantRestored,
                successNotificationTitle: 'Environment restored',
                successNotificationBody: 'The environment is available again in normal environment management flows and can be selected as active context.',
                modalHeading: 'Restore environment',
                modalDescription: 'Restore this archived environment so it can be selected again in normal environment management flows.',
                group: $group,
            );
        }

        $archiveOutcome = $this->tenantOperabilityService->outcomeFor(
            tenant: $context->tenant,
            question: TenantOperabilityQuestion::ArchiveEligibility,
            actor: $context->actor,
            workspaceId: $context->workspaceId,
            lane: $context->lane,
        );

        if ($this->shouldExposeAction($archiveOutcome)) {
            return new TenantActionDescriptor(
                key: 'archive',
                family: TenantActionFamily::LifecycleManagement,
                label: 'Archive',
                icon: 'heroicon-o-archive-box-x-mark',
                destructive: true,
                requiresConfirmation: true,
                auditActionId: AuditActionId::TenantArchived,
                successNotificationTitle: 'Environment archived',
                successNotificationBody: 'The environment remains available for inspection and audit history, but it is no longer selectable as active context.',
                modalHeading: 'Archive environment',
                modalDescription: 'Archive this environment to retain it for inspection and audit history while removing it from active management flows.',
                group: $group,
            );
        }

        return null;
    }

    private function relatedOnboardingActionForContext(
        TenantActionContext $context,
        string $group = 'overflow',
    ): ?TenantActionDescriptor {
        $draft = $context->relatedOnboardingDraft;

        if (! $draft instanceof ManagedEnvironmentOnboardingSession) {
            return null;
        }

        $resumeOutcome = $this->tenantOperabilityService->outcomeFor(
            tenant: $context->tenant,
            question: TenantOperabilityQuestion::ResumeOnboardingEligibility,
            actor: $context->actor,
            workspaceId: $context->workspaceId,
            lane: $context->lane,
            onboardingDraft: $draft,
        );

        if ($resumeOutcome->allowed) {
            return new TenantActionDescriptor(
                key: 'related_onboarding',
                family: TenantActionFamily::OnboardingWorkflow,
                label: 'Resume onboarding',
                icon: 'heroicon-o-arrow-path',
                group: $group,
            );
        }

        if ($draft->isCancelled()) {
            return new TenantActionDescriptor(
                key: 'related_onboarding',
                family: TenantActionFamily::OnboardingWorkflow,
                label: 'View cancelled onboarding draft',
                icon: 'heroicon-o-eye',
                group: $group,
            );
        }

        if ($draft->isCompleted()) {
            return new TenantActionDescriptor(
                key: 'related_onboarding',
                family: TenantActionFamily::OnboardingWorkflow,
                label: 'View completed onboarding',
                icon: 'heroicon-o-eye',
                group: $group,
            );
        }

        return new TenantActionDescriptor(
            key: 'related_onboarding',
            family: TenantActionFamily::OnboardingWorkflow,
            label: 'View related onboarding',
            icon: 'heroicon-o-eye',
            group: $group,
        );
    }

    private function shouldExposeAction(TenantOperabilityOutcome $outcome): bool
    {
        return $outcome->allowed || $outcome->isDeniedForCapability();
    }
}
