<?php

namespace App\Modules\Admin\Operations\Services;

use App\Models\User;
use App\Modules\Admin\Operations\Repositories\PlatformAuditRepository;
use App\Modules\Admin\Operations\Repositories\SupportImpersonationSessionRepository;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;

class SupportImpersonationService
{
    private const AUDIT_CAT = 'support_impersonation';

    private const SESSION_KEY = 'support_impersonation';

    public function __construct(
        private readonly SupportImpersonationSessionRepository $sessions,
        private readonly PlatformAuditRepository $audit,
    ) {}

    public function begin(User $admin, int $targetUserId, string $reason, ?string $ip, ?string $userAgent): string
    {
        $target = User::query()->whereKey($targetUserId)->where('is_active', true)->firstOrFail();
        if ($target->isAdmin()) {
            abort(422, 'Cannot impersonate platform administrators.');
        }
        $plain = Str::random(48);
        $expires = now()->addMinutes((int) config('tenancy.impersonation_ttl_minutes', 30));
        $row = $this->sessions->create([
            'admin_user_id' => (int) $admin->id,
            'target_user_id' => (int) $target->id,
            'token_hash' => hash('sha256', $plain),
            'reason' => $reason,
            'expires_at' => $expires,
            'ip_address' => $ip,
            'user_agent' => $userAgent !== null ? substr($userAgent, 0, 500) : null,
        ]);
        $this->audit->record((int) $admin->id, (int) $target->tenant_id, self::AUDIT_CAT, 'session.created', [
            'session_id' => (int) $row->id,
            'target_user_id' => (int) $target->id,
        ]);

        return $plain;
    }

    public function consume(User $admin, string $plainToken): void
    {
        $hash = hash('sha256', $plainToken);
        $row = $this->sessions->findActiveByTokenHash($hash);
        if (
            $row === null
            || $row->admin_user_id !== (int) $admin->id
            || $row->isExpired()
            || ! $row->target?->is_active
        ) {
            abort(403, 'Invalid or expired impersonation token.');
        }
        DB::transaction(function () use ($row, $admin): void {
            $row->forceFill(['consumed_at' => now()])->save();
            $this->audit->record((int) $admin->id, null, self::AUDIT_CAT, 'session.consumed', [
                'session_id' => (int) $row->id,
                'target_user_id' => (int) $row->target_user_id,
            ]);
        });
        Auth::loginUsingId((int) $row->target_user_id);
        Session::put(self::SESSION_KEY, [
            'original_user_id' => (int) $admin->id,
            'session_id' => (int) $row->id,
        ]);
        Session::regenerate();
    }

    public function end(): void
    {
        $meta = Session::pull(self::SESSION_KEY);
        if (! is_array($meta) || ! isset($meta['original_user_id'], $meta['session_id'])) {
            return;
        }
        $row = $this->sessions->findByIdForAdmin((int) $meta['session_id'], (int) $meta['original_user_id']);
        if ($row !== null) {
            $row->forceFill(['ended_at' => now()])->save();
            $this->audit->record((int) $meta['original_user_id'], null, self::AUDIT_CAT, 'session.ended', [
                'session_id' => (int) $row->id,
            ]);
        }
        Auth::loginUsingId((int) $meta['original_user_id']);
        Session::regenerate();
    }

    public function revoke(User $admin, int $sessionId): void
    {
        $row = $this->sessions->findByIdForAdmin($sessionId, (int) $admin->id);
        if ($row === null || $row->consumed_at !== null) {
            abort(404);
        }
        $row->forceFill(['revoked_at' => now()])->save();
        $this->audit->record((int) $admin->id, null, self::AUDIT_CAT, 'session.revoked', ['session_id' => $sessionId]);
    }
}
