<?php

declare(strict_types=1);

namespace App\Services\Auth;

use App\Models\PlatformUser;
use App\Models\SupportAccessGrant;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId;
use App\Support\Audit\AuditActorType;
use App\Support\Auth\Capabilities;
use App\Support\Auth\PlatformCapabilities;
use App\Support\Auth\WorkspaceRole;
use Carbon\CarbonImmutable;
use DomainException;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class SupportAccessGrantManager
{
    public function __construct(
        private readonly SupportAccessGrantResolver $resolver,
        private readonly WorkspaceAuditLogger $auditLogger,
        private readonly WorkspaceCapabilityResolver $workspaceCapabilities,
        private readonly BreakGlassSession $breakGlassSession,
    ) {}

    public function request(
        Workspace $workspace,
        PlatformUser $actor,
        string $scope,
        string $reason,
        int $ttlMinutes,
        ?string $waiverReason = null,
    ): SupportAccessGrant {
        $this->authorizePlatformActor($actor);

        $scope = $this->resolver->validateScope($scope);
        $reason = $this->validatedText($reason, 'reason');
        $ttlMinutes = $this->validatedTtl($ttlMinutes);
        $approvalMode = $this->resolver->approvalModeFor($workspace, $scope);
        $waiverReason = $waiverReason !== null ? trim($waiverReason) : null;

        if ($approvalMode === SupportAccessGrant::APPROVAL_MODE_OWNERLESS_WAIVER) {
            if (! $this->breakGlassSession->isActive()) {
                throw ValidationException::withMessages([
                    'scope' => 'Ownerless workspace recovery requires active break-glass mode.',
                ]);
            }

            $waiverReason = $this->validatedText((string) $waiverReason, 'waiver_reason');
        }

        if ($approvalMode !== SupportAccessGrant::APPROVAL_MODE_OWNERLESS_WAIVER) {
            $waiverReason = null;
        }

        $this->resolver->expireStaleActiveGrants($workspace);

        $existing = SupportAccessGrant::query()
            ->where('workspace_id', (int) $workspace->getKey())
            ->where('requested_by_platform_user_id', (int) $actor->getKey())
            ->where('scope', $scope)
            ->open()
            ->latest('id')
            ->first();

        if ($existing instanceof SupportAccessGrant) {
            return $existing;
        }

        return DB::transaction(function () use ($workspace, $actor, $scope, $reason, $ttlMinutes, $approvalMode, $waiverReason): SupportAccessGrant {
            $now = CarbonImmutable::now();
            $activeImmediately = in_array($approvalMode, [
                SupportAccessGrant::APPROVAL_MODE_AUTO,
                SupportAccessGrant::APPROVAL_MODE_OWNERLESS_WAIVER,
            ], true);

            $grant = SupportAccessGrant::query()->create([
                'workspace_id' => (int) $workspace->getKey(),
                'requested_by_platform_user_id' => (int) $actor->getKey(),
                'approved_by_user_id' => null,
                'scope' => $scope,
                'status' => $activeImmediately
                    ? SupportAccessGrant::STATUS_ACTIVE
                    : SupportAccessGrant::STATUS_REQUESTED,
                'approval_mode' => $approvalMode,
                'reason' => $reason,
                'waiver_reason' => $waiverReason,
                'ttl_minutes' => $ttlMinutes,
                'requested_at' => $now,
                'approved_at' => null,
                'starts_at' => $activeImmediately ? $now : null,
                'expires_at' => $activeImmediately ? $now->addMinutes($ttlMinutes) : null,
            ]);

            $this->audit($grant, AuditActionId::SupportAccessRequested, $actor, 'Support access requested for '.$workspace->name);

            if ($approvalMode === SupportAccessGrant::APPROVAL_MODE_OWNERLESS_WAIVER) {
                $this->audit($grant, AuditActionId::SupportAccessOwnerlessWaiverUsed, $actor, 'Ownerless support-access waiver used for '.$workspace->name);
            }

            if ($activeImmediately) {
                $this->audit($grant, AuditActionId::SupportAccessActivated, $actor, 'Support access activated for '.$workspace->name);
            }

            return $grant;
        });
    }

    public function approve(SupportAccessGrant $grant, User $actor): SupportAccessGrant
    {
        $this->authorizeWorkspaceOwnerApproval($grant, $actor);

        if ($grant->status !== SupportAccessGrant::STATUS_REQUESTED) {
            throw new DomainException('Only pending support-access requests may be approved.');
        }

        if ($grant->scope !== SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY) {
            throw new DomainException('Only recovery support-access requests require owner approval.');
        }

        return DB::transaction(function () use ($grant, $actor): SupportAccessGrant {
            $now = CarbonImmutable::now();

            $grant->forceFill([
                'status' => SupportAccessGrant::STATUS_ACTIVE,
                'approved_by_user_id' => (int) $actor->getKey(),
                'approved_at' => $now,
                'starts_at' => $now,
                'expires_at' => $now->addMinutes((int) $grant->ttl_minutes),
            ])->save();

            $this->audit($grant->fresh(), AuditActionId::SupportAccessApproved, $actor, 'Support access approved for '.$grant->workspace->name);
            $this->audit($grant->fresh(), AuditActionId::SupportAccessActivated, $actor, 'Support access activated for '.$grant->workspace->name);

            return $grant->fresh();
        });
    }

    public function deny(SupportAccessGrant $grant, User $actor): SupportAccessGrant
    {
        $this->authorizeWorkspaceOwnerApproval($grant, $actor);

        if ($grant->status !== SupportAccessGrant::STATUS_REQUESTED) {
            throw new DomainException('Only pending support-access requests may be denied.');
        }

        return DB::transaction(function () use ($grant, $actor): SupportAccessGrant {
            $grant->forceFill([
                'status' => SupportAccessGrant::STATUS_DENIED,
                'denied_at' => CarbonImmutable::now(),
            ])->save();

            $this->audit($grant->fresh(), AuditActionId::SupportAccessDenied, $actor, 'Support access denied for '.$grant->workspace->name);

            return $grant->fresh();
        });
    }

    public function end(SupportAccessGrant $grant, PlatformUser $actor): SupportAccessGrant
    {
        $this->authorizePlatformActor($actor);
        $this->resolver->expireStaleActiveGrants($grant->workspace);

        $grant = $grant->fresh();

        if (! $grant instanceof SupportAccessGrant || $grant->status !== SupportAccessGrant::STATUS_ACTIVE) {
            throw new DomainException('Only active support-access grants may be ended.');
        }

        return DB::transaction(function () use ($grant, $actor): SupportAccessGrant {
            $grant->forceFill([
                'status' => SupportAccessGrant::STATUS_ENDED,
                'ended_at' => CarbonImmutable::now(),
            ])->save();

            $this->audit($grant->fresh(), AuditActionId::SupportAccessEnded, $actor, 'Support access ended for '.$grant->workspace->name);

            return $grant->fresh();
        });
    }

    private function authorizePlatformActor(PlatformUser $actor): void
    {
        if (! $actor->hasCapability(PlatformCapabilities::SUPPORT_ACCESS_MANAGE)) {
            abort(403);
        }
    }

    private function authorizeWorkspaceOwnerApproval(SupportAccessGrant $grant, User $actor): void
    {
        $workspace = $grant->workspace;

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

        if (! $this->workspaceCapabilities->isMember($actor, $workspace)) {
            abort(404);
        }

        if (! $this->workspaceCapabilities->can($actor, $workspace, Capabilities::WORKSPACE_SETTINGS_MANAGE)) {
            abort(403);
        }

        if ($this->workspaceCapabilities->getRole($actor, $workspace) !== WorkspaceRole::Owner) {
            abort(403);
        }
    }

    private function validatedText(string $value, string $field): string
    {
        $trimmed = trim($value);

        if (mb_strlen($trimmed) < 5) {
            throw ValidationException::withMessages([
                $field => 'Enter at least 5 characters.',
            ]);
        }

        if (mb_strlen($trimmed) > 500) {
            throw ValidationException::withMessages([
                $field => 'Enter no more than 500 characters.',
            ]);
        }

        return $trimmed;
    }

    private function validatedTtl(int $ttlMinutes): int
    {
        $max = $this->resolver->maxTtlMinutes();

        if ($ttlMinutes < 1 || $ttlMinutes > $max) {
            throw ValidationException::withMessages([
                'ttl_minutes' => 'TTL must be between 1 and '.$max.' minutes.',
            ]);
        }

        return $ttlMinutes;
    }

    private function audit(SupportAccessGrant $grant, AuditActionId $action, User|PlatformUser $actor, string $summary): void
    {
        $workspace = $grant->workspace;

        if (! $workspace instanceof Workspace) {
            return;
        }

        $this->auditLogger->log(
            workspace: $workspace,
            action: $action,
            context: $this->resolver->auditContext($grant),
            actor: $actor,
            status: 'success',
            resourceType: 'support_access_grant',
            resourceId: (string) $grant->getKey(),
            actorType: $actor instanceof PlatformUser ? AuditActorType::Platform : null,
            targetLabel: $this->resolver->targetLabel($grant),
            summary: $summary,
        );
    }
}
