<?php

namespace Modules\SaasSwitcher\Support;

use App\Business;
use Illuminate\Support\Facades\Auth;

/**
 * Per-business module entitlements gate.
 *
 * Answers "may the active business see/reach module X?" as a DATA lookup against
 * the business's `module_entitlements` column — deliberately NOT a permission
 * check, so it is immune to the `Admin#<business>` Gate::before bypass in
 * App\Providers\AuthServiceProvider. Per-user permissions remain the second axis
 * (the existing `can('*.access_module')` gates); entitlement is the orthogonal
 * "did this tenant get this module" axis.
 *
 * Lives entirely inside the SaasSwitcher module (no core-script changes). The
 * `module_entitlements` column is read raw + decoded here, so the core
 * App\Business model needs no cast.
 *
 * Fail-open by design: an unconfigured business (NULL column) is fully entitled;
 * an explicit empty list ([]) means entitled to nothing (clean base ERP); and
 * any module not in the gateable registry is never restricted.
 */
class ModuleEntitlementService
{
    /** Request-scoped memo of resolved lists keyed by business id. */
    protected array $memo = [];

    /**
     * The active business's entitlement list, or null when unset (= fully entitled).
     */
    public function entitledModules(): ?array
    {
        try {
            // Web path: the business model is in the session (App\Http\Middleware\SetSessionData).
            if (session()->has('business')) {
                $business = session('business');
                $raw = is_object($business)
                    ? ($business->module_entitlements ?? null)
                    : (is_array($business) ? ($business['module_entitlements'] ?? null) : null);

                return $this->normalize($raw);
            }

            // No-session path (e.g. token-authenticated staff API): resolve by user's business.
            $user = Auth::user();
            if ($user && ! empty($user->business_id)) {
                $bizId = $user->business_id;
                if (! array_key_exists($bizId, $this->memo)) {
                    $raw = optional(Business::find($bizId))->getAttributes()['module_entitlements'] ?? null;
                    $this->memo[$bizId] = $this->normalize($raw);
                }

                return $this->memo[$bizId];
            }
        } catch (\Throwable $e) {
            return null; // fail-open on any error
        }

        return null;
    }

    /**
     * Is the given module visible/reachable for the active business?
     *
     * Fail-open: modules not in the gateable registry are always allowed, and an
     * unconfigured business (NULL column) is entitled to everything.
     */
    public function isModuleEntitled(string $module): bool
    {
        // Only registered "gateable" modules are ever restricted.
        if (! array_key_exists($module, $this->gateableModules())) {
            return true;
        }

        $list = $this->entitledModules();
        if ($list === null) {
            return true; // unconfigured (NULL column) => fully entitled (fail-open)
        }

        // An explicit list (including an empty []) is authoritative: [] = entitled
        // to nothing (used to give the host/static project a clean base ERP).
        return in_array($module, $list, true);
    }

    /** The gateable-module registry (Modules/SaasSwitcher/Config/config.php). */
    public function gateableModules(): array
    {
        return (array) config('saasswitcher.gateable_modules', []);
    }

    /** Coerce a stored value (array, JSON string, or null) into a clean list or null. */
    protected function normalize($list): ?array
    {
        if (is_string($list)) {
            $decoded = json_decode($list, true);
            $list = is_array($decoded) ? $decoded : null;
        }

        return is_array($list) ? array_values($list) : null;
    }
}
