<?php

namespace App\Http\Middleware;

use App\Models\ClientAuthorization;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class ClientAccountSwitcher
{
    /**
     * Runtime-extensible permission registry.
     * Addon system populates this via registerPermission() at boot/per-request.
     *
     * @var array<string, array<int, string>>
     */
    protected static array $extraPermissions = [];

    public static function registerPermission(string $routePattern, string|array $permissions): void
    {
        self::$extraPermissions[$routePattern] = is_array($permissions) ? $permissions : [$permissions];
    }

    public static function getExtraPermissions(): array
    {
        return self::$extraPermissions;
    }

    public static function resetExtraPermissions(): void
    {
        self::$extraPermissions = [];
    }

    public function handle(Request $request, Closure $next): Response
    {
        if (! Auth::guard('client')->check()) {
            return $next($request);
        }

        $authenticatedClient = Auth::guard('client')->user();
        $requestedClientId = $request->input('switch_account') ?? session('active_client_id');

        // PRC-188: per-request debug logging kaldırıldı — `LOG_LEVEL=debug`
        // set edilen prod kurulumlarında her authenticated client request'i
        // log'a yazıyordu. Disk-fill DoS + log noise. Kritik vakalar
        // (Log::warning + Log::create audit) zaten korunuyor.

        if ($requestedClientId && $requestedClientId != $authenticatedClient->id) {
            $authorization = ClientAuthorization::where('owner_client_id', $requestedClientId)
                ->where('authorized_client_id', $authenticatedClient->id)
                ->where('status', 'active')
                ->whereNull('revoked_at')
                ->first();

            // PRC-842 — switch BRAND SINIRINI AŞAMAZ: hedef hesap farklı bir
            // brand'e (veya ana siteye) aitse yetki kaydı olsa bile reddet
            // (whitelabel izolasyonu: brand müşterisi ana site hesabına, ana
            // müşteri brand hesabına geçemez).
            if ($authorization) {
                $ownerBrandId = \App\Models\Client::whereKey($requestedClientId)->value('brand_id');
                if ((int) ($ownerBrandId ?? 0) !== (int) ($authenticatedClient->brand_id ?? 0)) {
                    $authorization = null;
                }
            }

            if ($authorization) {
                // Verify permission for the current route/action
                $hasPermission = $this->hasPermissionForCurrentAction($request, $authorization->permissions ?? []);

                // PRC-188: per-request debug log kaldırıldı; failed-permission
                // log'u aşağıdaki Log::warning + Log::TYPE_CLIENT_ACCOUNT_SWITCH_DENIED
                // audit row'unda zaten kayda alınıyor.

                if (! $hasPermission) {
                    // PRC-199 — durable audit row: failed permission
                    // check inside a switched session is a privilege-
                    // escalation signal. Owner sees this in their
                    // activity timeline (client_id = owner).
                    \App\Models\Log::create([
                        'type' => \App\Models\Log::TYPE_CLIENT_ACCOUNT_SWITCH_DENIED,
                        'observer' => 'ClientAccountSwitcher',
                        'module' => 'account_switch',
                        'method' => 'permission_denied',
                        'client_id' => $requestedClientId,
                        'ip_address' => $request->ip() ?? '0.0.0.0',
                        'user_agent' => $request->userAgent(),
                        'session_id' => session()->getId(),
                        'description' => "Switched client #{$authenticatedClient->id} denied access to {$request->route()->getName()} on owner #{$requestedClientId}",
                        'request' => [
                            'authorized_client_id' => $authenticatedClient->id,
                            'owner_client_id' => $requestedClientId,
                            'authorization_id' => $authorization->id,
                            'route' => $request->route()->getName(),
                            'required_permissions' => $authorization->permissions ?? [],
                        ],
                    ]);

                    \Log::warning('Access denied to route', [
                        'route' => $request->route()->getName(),
                        'required_permissions' => $authorization->permissions ?? [],
                        'authenticated_user' => $authenticatedClient->id,
                        'target_client' => $requestedClientId,
                    ]);
                    abort(403, 'Access denied to this function');
                }

                // PRC-622 Dalga B — ClientLoginShare: yalnız EXPLICIT geçiş
                // anında (her request'te session yenilenirken değil) fire eder.
                $isExplicitSwitch = (bool) $request->input('switch_account')
                    && session('active_client_id') != $requestedClientId;

                // Store both active client and authorization info in session
                session([
                    'active_client_id' => $requestedClientId,
                    'active_authorization_id' => $authorization->id,
                    'active_permissions' => $authorization->permissions ?? [],
                    'switch_timestamp' => now()->timestamp,
                    'original_client_id' => $authenticatedClient->id,
                ]);

                if ($isExplicitSwitch) {
                    app(\App\Core\Hook\HookBridge::class)->fireAction('ClientLoginShare', [
                        'userid'               => (int) $requestedClientId,
                        'user_id'              => (int) $requestedClientId,
                        'owner_client_id'      => (int) $requestedClientId,
                        'switched_client_id'   => $authenticatedClient->id,
                        'authorized_client_id' => $authenticatedClient->id,
                        'authorization_id'     => $authorization->id,
                    ]);
                }
            } else {
                $this->clearSwitchSession();
                if ($request->input('switch_account')) {
                    abort(403, 'Unauthorized account access');
                }
            }
        } else {
            $this->clearSwitchSession();
        }

        // Session hijacking protection
        if (session('active_client_id') && session('switch_timestamp')) {
            // Expire sessions after 4 hours
            if (now()->timestamp - session('switch_timestamp') > 14400) {
                $this->clearSwitchSession();
                abort(403, 'Session expired. Please switch account again.');
            }
        }

        return $next($request);
    }

    private function hasPermissionForCurrentAction(Request $request, array $permissions): bool
    {
        $routeName = $request->route()->getName();

        if (! $routeName) {
            // Unnamed route under account switcher — deny by default
            return false;
        }

        // Always allow basic navigation and account switching
        $basicRoutes = [
            'store.auth.dashboard',
            'store.auth.switch-account',
            'store.auth.account-list',
            'store.auth.network-status',
        ];

        if (in_array($routeName, $basicRoutes)) {
            return true;
        }

        // Also check addon registry for __BASIC__ patterns
        foreach (self::$extraPermissions as $pattern => $perms) {
            if (in_array('__BASIC__', $perms, true) && \Illuminate\Support\Str::is($pattern, $routeName)) {
                return true;
            }
        }

        // Import permission constants
        $P = \App\Enums\ClientPermission::class;

        // Define permission mappings for different route patterns.
        // Order matters — first pattern matching the route name wins.
        $permissionMap = [
            // --- ALWAYS ALLOWED: a switched user's "my authorizations" page —
            // this is the user's OWN surface (not the owner's), so they must be
            // able to reach it in switched mode to leave the account, update
            // their own email preferences, etc. Must match BEFORE the
            // profile.* BLOCKED wildcard below.
            'store.auth.profile.my-authorizations' => ['__BASIC__'],
            'store.auth.profile.my-authorizations.*' => ['__BASIC__'],

            // --- BLOCKED: personal / owner-only surfaces ---
            // Consents are owner-only — switched users can't manage owner's marketing consents.
            // Must come BEFORE the profile.* wildcard (first match wins).
            'store.auth.profile.consents.*' => ['__BLOCKED__'],
            // Profile, 2FA, sessions, notifications — belong to owner only
            'store.auth.profile.*' => ['__BLOCKED__'],
            'store.auth.2fa.*' => ['__BLOCKED__'],
            // Affiliate & reseller are personal programs
            'store.auth.affiliate.*' => ['__BLOCKED__'],
            // Reseller agreement gate — owner-only (switched users can't accept on owner's behalf)
            'store.auth.reseller.agreement.*' => ['__BLOCKED__'],
            'store.auth.reseller.*' => ['__BLOCKED__'],
            // Balance / account-closure / data-privacy / email history — owner only
            'store.auth.add-funds.*' => ['__BLOCKED__'],
            'store.auth.account-closure' => ['__BLOCKED__'],
            'store.auth.account-closure.*' => ['__BLOCKED__'],
            'store.auth.data-privacy' => ['__BLOCKED__'],
            'store.auth.data-privacy.*' => ['__BLOCKED__'],
            'store.auth.emails' => ['__BLOCKED__'],
            'store.auth.emails.*' => ['__BLOCKED__'],
            // Identity documents are personal
            'store.auth.verifications.documents.download' => ['__BLOCKED__'],
            // Note: 'store.auth.profile.invitation' is already covered by the
            // 'store.auth.profile.*' wildcard above.

            // --- SERVICES ---
            // Read-only views
            'store.auth.services' => [$P::VIEW_SERVICES],
            'store.auth.services.details' => [$P::VIEW_SERVICES],
            'store.auth.services.resources' => [$P::VIEW_SERVICES],
            'store.auth.services.action.show' => [$P::VIEW_SERVICES],
            'store.auth.services.cancel' => [$P::VIEW_SERVICES],
            'store.auth.services.upgrade' => [$P::VIEW_SERVICES],
            // Mutating operations require manage
            'store.auth.services.auto-renew' => [$P::MANAGE_SERVICES],
            'store.auth.services.remove' => [$P::MANAGE_SERVICES], // PRC-752
            'store.auth.services.action.handle' => [$P::MANAGE_SERVICES],
            'store.auth.services.cancel.store' => [$P::MANAGE_SERVICES],
            'store.auth.services.cancel.withdraw' => [$P::MANAGE_SERVICES],
            'store.auth.services.upgrade.preview' => [$P::MANAGE_SERVICES],
            'store.auth.services.upgrade.execute' => [$P::MANAGE_SERVICES],
            // PRC-237 — SSL install (PRC-57) + Software activate (PRC-59) for
            // service-bound credentials. View pages and key download (read)
            // require VIEW_SERVICES; the actual mutation requires MANAGE_SERVICES.
            'store.auth.services.ssl.install.show' => [$P::VIEW_SERVICES],
            'store.auth.services.ssl.key-download' => [$P::VIEW_SERVICES],
            'store.auth.services.ssl.install' => [$P::MANAGE_SERVICES],
            'store.auth.services.software.activate.show' => [$P::VIEW_SERVICES],
            'store.auth.services.software.activate' => [$P::MANAGE_SERVICES],

            // --- DOMAINS ---
            // Read-only views
            'store.auth.domains' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.nameservers' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.lock' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.privacy' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.contacts' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.dns' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.child-nameservers' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.epp-code' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.email-forwarding' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.url-forwarding' => [$P::VIEW_DOMAINS],
            'store.auth.domains.manage.renewal' => [$P::VIEW_DOMAINS],
            'store.auth.domains.details' => [$P::VIEW_DOMAINS],
            'store.auth.domains.email-forwarding' => [$P::VIEW_DOMAINS],
            'store.auth.domains.renew' => [$P::VIEW_DOMAINS],
            // Mutating operations require manage
            'store.auth.domains.manage.nameservers.update' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.lock.update' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.privacy.update' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.contacts.update' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.dns.add' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.dns.update' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.dns.delete' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.child-nameservers.add' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.child-nameservers.update' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.child-nameservers.delete' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.email-forwarding.add' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.email-forwarding.delete' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.url-forwarding.save' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.manage.url-forwarding.delete' => [$P::MANAGE_DOMAINS],
            'store.auth.domains.auto-renew' => [$P::MANAGE_DOMAINS],

            // --- INVOICES / QUOTES / CONTRACTS / DOWNLOADS (billing-adjacent) ---
            'store.auth.invoices' => [$P::VIEW_INVOICES],
            'store.auth.invoices.details' => [$P::VIEW_INVOICES],
            'store.auth.invoices.download' => [$P::VIEW_INVOICES],
            'store.auth.invoices.legal-pdf' => [$P::VIEW_INVOICES],
            // PRC-380: snapshot edit (before payment, requires MANAGE_INVOICES)
            'store.auth.invoices.edit-snapshot' => [$P::MANAGE_INVOICES],
            'store.auth.invoices.update-snapshot' => [$P::MANAGE_INVOICES],
            'store.auth.quotes' => [$P::VIEW_INVOICES],
            'store.auth.quotes.*' => [$P::VIEW_INVOICES],
            // Pending contracts is owner-only (login-gate legal binding) —
            // switched users cannot accept on the owner's behalf.
            // Specific patterns must come BEFORE the wildcard (first match wins).
            'store.auth.contracts.pending' => ['__BLOCKED__'],
            'store.auth.contracts.pending.accept' => ['__BLOCKED__'],
            'store.auth.contracts.pending.skip' => ['__BLOCKED__'],
            'store.auth.contracts.accept' => ['__BLOCKED__'], // legacy /contracts/{slug}/accept — recording legally binding acceptance is owner-only
            'store.auth.contracts' => [$P::VIEW_INVOICES],
            'store.auth.contracts.*' => [$P::VIEW_INVOICES],
            'store.auth.downloads' => [$P::VIEW_SERVICES],
            'store.auth.downloads.*' => [$P::VIEW_SERVICES],

            // --- PAYMENT (Store/PaymentController, prefix `store.payment.*`) — PRC-208 ---
            // Switched user view'a `view_invoices`, ödeme/process'e `manage_invoices`.
            'store.payment.show' => [$P::VIEW_INVOICES],
            'store.payment.success' => [$P::VIEW_INVOICES],
            'store.payment.failed' => [$P::VIEW_INVOICES],
            'store.payment.callback.success' => [$P::VIEW_INVOICES],
            'store.payment.callback.fail' => [$P::VIEW_INVOICES],
            'store.payment.process' => [$P::MANAGE_INVOICES],
            'store.payment.change-gateway' => [$P::MANAGE_INVOICES],
            'store.payment.*' => [$P::VIEW_INVOICES], // catch-all fallback

            // --- CHECKOUT (Store/CheckoutController, prefix `store.checkout.*`) — PRC-211 ---
            // BLOCKED for switched users — a purchase is a legally binding
            // commitment by the owner; the authorized user cannot place an
            // order on the owner's behalf. Owner must log in directly.
            'store.checkout.index' => ['__BLOCKED__'],
            'store.checkout.process' => ['__BLOCKED__'],
            'store.checkout.update-billing' => ['__BLOCKED__'],
            'store.checkout.installment-plans' => ['__BLOCKED__'],
            'store.checkout.*' => ['__BLOCKED__'], // catch-all fallback

            // --- MASS PAYMENT (Store/ClientAreaController, prefix `store.mass-payment.*`) — PRC-211 ---
            // Same semantics as single-invoice payment: view_invoices for the
            // summary screen, manage_invoices for the actual process step.
            'store.mass-payment.show' => [$P::VIEW_INVOICES],
            'store.mass-payment.process' => [$P::MANAGE_INVOICES],
            'store.mass-payment.*' => [$P::VIEW_INVOICES], // catch-all fallback

            // --- TICKETS (fine-grained) ---
            'store.auth.tickets' => [$P::VIEW_TICKETS],
            'store.auth.tickets.details' => [$P::VIEW_TICKETS],
            'store.auth.tickets.attachment.download' => [$P::VIEW_TICKETS],
            'store.auth.tickets.attachment.preview' => [$P::VIEW_TICKETS],
            'store.auth.tickets.reply' => [$P::VIEW_TICKETS],
            // Reply-write / close / reopen / rate need REPLY_TICKETS
            'store.auth.tickets.reply.store' => [$P::REPLY_TICKETS],
            'store.auth.tickets.rate' => [$P::REPLY_TICKETS],
            'store.auth.tickets.close' => [$P::REPLY_TICKETS],
            'store.auth.tickets.reopen' => [$P::REPLY_TICKETS],
            // Create endpoints need CREATE_TICKETS
            'store.auth.tickets.create' => [$P::CREATE_TICKETS],
            'store.auth.tickets.create.suggestions' => [$P::CREATE_TICKETS],
            'store.auth.tickets.store' => [$P::CREATE_TICKETS],
        ];

        // Merge runtime-registered addon permissions at end (lower priority — first-match-wins)
        $permissionMap = array_merge($permissionMap, self::$extraPermissions);

        foreach ($permissionMap as $pattern => $requiredPerms) {
            if (\Illuminate\Support\Str::is($pattern, $routeName)) {
                // Check for blocked routes
                if (in_array('__BLOCKED__', $requiredPerms)) {
                    return false;
                }

                // Routes that are always allowed regardless of permissions
                // (the user's own management surface while in switched mode).
                if (in_array('__BASIC__', $requiredPerms)) {
                    return true;
                }

                // Check required permissions (including granular permissions)
                foreach ($requiredPerms as $perm) {
                    if (in_array($perm, $permissions)) {
                        return true; // Has general permission
                    }

                    // Granular equivalents — only for VIEW_* (manage granular is still
                    // checked at controller level against the specific resource id).
                    if ($perm === $P::VIEW_SERVICES) {
                        $hasGranularService = collect($permissions)->contains(fn ($p) => str_starts_with($p, 'view_service'));
                        if ($hasGranularService) {
                            return true;
                        }
                    }

                    if ($perm === $P::VIEW_DOMAINS) {
                        $hasGranularDomain = collect($permissions)->contains(fn ($p) => str_starts_with($p, 'view_domain'));
                        if ($hasGranularDomain) {
                            return true;
                        }
                    }

                    if ($perm === $P::MANAGE_SERVICES) {
                        $hasGranularService = collect($permissions)->contains(fn ($p) => str_starts_with($p, 'manage_service'));
                        if ($hasGranularService) {
                            return true;
                        }
                    }

                    if ($perm === $P::MANAGE_DOMAINS) {
                        $hasGranularDomain = collect($permissions)->contains(fn ($p) => str_starts_with($p, 'manage_domain'));
                        if ($hasGranularDomain) {
                            return true;
                        }
                    }
                }

                return false;
            }
        }

        // Unmapped routes: deny by default (fail-closed). If a new route is added
        // and the switched user should see it, add an explicit mapping above.
        \Log::warning('Unmapped route in ClientAccountSwitcher — denied', [
            'route' => $routeName,
            'permissions' => $permissions,
        ]);

        return false;
    }

    private function clearSwitchSession(): void
    {
        session()->forget([
            'active_client_id',
            'active_authorization_id',
            'active_permissions',
            'switch_timestamp',
            'original_client_id',
            // PRC-196 — was leaking past switch_ip into the next switched
            // session. No functional impact today (SwitchAccountRequest
            // is the only reader), but a stale IP key surviving across
            // sessions is the kind of thing future refactors quietly
            // misread.
            'switch_ip',
        ]);
    }
}
