<?php

namespace App\Http\Middleware;

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

/**
 * Automatically checks Spatie permissions based on route names.
 *
 * Route name format: admin.{module}.{action}
 * Maps to permission: {module}.{action-mapped}
 *
 * Action mapping:
 *   index, show       → view
 *   create, store     → create
 *   edit, update      → update
 *   destroy, bulk-destroy → delete
 *   export            → export
 *   import            → import
 *   refund            → refund
 *   toggle, configure → update
 */
class EnsureAdminPermission
{
    /**
     * Map route action names to permission action names.
     */
    private const ACTION_MAP = [
        'index'        => 'view',
        'show'         => 'view',
        'create'       => 'create',
        'store'        => 'create',
        'edit'         => 'update',
        'update'       => 'update',
        'destroy'      => 'delete',
        'bulk-destroy' => 'delete',
        'export'       => 'export',
        'import'       => 'import',
        'refund'       => 'refund',
        'toggle'       => 'update',
        'configure'    => 'update',
    ];

    /**
     * Routes/modules that don't require specific permission checks.
     * These are accessible to any authenticated admin.
     */
    private const EXEMPT_ROUTES = [
        'admin.dashboard',
        'admin.login',
        'admin.admin-logout',
        'admin.test-dashboard',
    ];

    public function handle(Request $request, Closure $next): Response
    {
        $user = auth()->user();

        // Recognise BOTH guards' user shapes:
        //   - 'web'   guard → App\Models\User  (has $is_admin, $admin_role)
        //   - 'admin' guard → App\Models\Admin (has $role, NO $is_admin)
        //
        // On /admin/* routes guarded by auth:admin, auth()->user() returns
        // the Admin model. The Admin model has no `is_admin` column, so the
        // old check (!$user->is_admin) was always TRUE → abort(403) →
        // "Access Denied" page after a successful login. The fix is to
        // treat presence of an authenticated Admin record as proof the
        // user belongs in the admin area.
        $isAdminUser = $user instanceof Admin
            || (Auth::guard('admin')->check())
            || (! empty($user?->is_admin));

        if (!$user || !$isAdminUser) {
            Log::warning('EnsureAdminPermission: ABORT 403 — no admin identity', [
                'has_user'   => (bool) $user,
                'user_class' => $user ? get_class($user) : null,
                'is_admin'   => $user?->is_admin ?? null,
                'admin_guard_check' => Auth::guard('admin')->check(),
            ]);
            abort(403, 'Unauthorized.');
        }

        // Resolve the effective role from whichever model is active.
        // Admin uses $role, User uses $admin_role.
        $effectiveRole = $user instanceof Admin
            ? ($user->role ?? null)
            : ($user->admin_role ?? null);

        // Super Admin bypasses all permission checks. hasRole() only
        // exists on the User model (Spatie HasRoles trait); guard so
        // calling it on an Admin instance doesn't throw.
        $hasSpatieSuperAdmin = false;
        if (method_exists($user, 'hasRole')) {
            try {
                $hasSpatieSuperAdmin = $user->hasRole('Super Admin');
            } catch (\Throwable $e) {
                // Spatie tables can be empty on a fresh install — degrade
                // gracefully to the role-column check.
            }
        }

        if ($effectiveRole === 'super_admin' || $hasSpatieSuperAdmin) {
            return $next($request);
        }

        $routeName = $request->route()?->getName();

        if (!$routeName) {
            return $next($request);
        }

        // Exempt routes don't need permission checks
        if (in_array($routeName, self::EXEMPT_ROUTES, true)) {
            return $next($request);
        }

        // Parse route name: admin.{module}.{action}
        $permission = $this->resolvePermission($routeName);

        // Only the User model carries Spatie permissions. When the active
        // user is an Admin (admin guard), we have no permission system
        // beyond super_admin / support roles — fall back to role-level
        // gating instead of $user->can(...).
        $hasPermission = true;
        if ($permission) {
            if (method_exists($user, 'can')) {
                $hasPermission = $user->can($permission);
            } else {
                // Admin model has no per-permission gates — non-super_admin
                // admins (e.g. 'support') get read-only access to a fixed
                // subset enforced by AdminMiddleware. Treat as allowed
                // here and let downstream code handle finer gating.
                $hasPermission = true;
            }
        }

        if ($permission && !$hasPermission) {
            if ($request->expectsJson()) {
                return response()->json([
                    'message' => 'You do not have permission to perform this action.',
                ], 403);
            }

            return redirect()->route('admin.dashboard')
                ->with('error', 'You do not have permission to access that section.');
        }

        return $next($request);
    }

    /**
     * Resolve a route name to a Spatie permission string.
     *
     * admin.users.index     → users.view
     * admin.roles.create    → roles.create
     * admin.settings.update → settings.update
     * admin.blocked-ips.store → blocked-ips.create
     */
    private function resolvePermission(string $routeName): ?string
    {
        // Remove 'admin.' prefix
        if (!str_starts_with($routeName, 'admin.')) {
            return null;
        }

        $parts = explode('.', substr($routeName, 6));

        if (count($parts) < 2) {
            // Single-segment route like admin.settings → settings.view
            $module = $parts[0] ?? '';
            return $module ? "{$module}.view" : null;
        }

        // Handle compound module names: blocked-ips, security-settings, etc.
        // Last segment is the action, everything before is the module
        $action = array_pop($parts);
        $module = implode('-', $parts);

        // Handle special cases where module has dots in route name
        // e.g., admin.payment-gateways.toggle → payment-gateways.update
        $mappedAction = self::ACTION_MAP[$action] ?? $action;

        return "{$module}.{$mappedAction}";
    }
}
