<?php

namespace App\Services\Tenancy;

use App\Models\Branch;
use App\Models\Central\Tenant;
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use App\Services\CurrentContext;
use App\Services\DocumentNumberService;
use Database\Seeders\LookupSeeder;
use Database\Seeders\PermissionsAndRolesSeeder;
use Database\Seeders\SystemCategorySeeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

/**
 * Onboards a tenant: seeds its per-tenant defaults (system roles, lookups, the
 * protected system catalog categories), grants its entitlement ceiling, and
 * creates the owner administrator — all inside the tenant's scope context so
 * every row is stamped with its tenant_id.
 *
 * Global vocabulary (permissions, menu_items) and shared geography are assumed
 * to already exist (seeded once at install). Document sequences are created
 * lazily by {@see DocumentNumberService}, so none are seeded here.
 */
class TenantProvisioner
{
    public function __construct(private readonly CurrentContext $context) {}

    /**
     * @param  array{name:string, email:string, password?:string}  $admin
     * @param  array<int, string>|null  $entitlementPermissionNames  null = grant everything (full ceiling)
     * @return array{tenant: Tenant, owner: User, password: string}
     */
    public function provision(Tenant $tenant, array $admin, ?array $entitlementPermissionNames = null): array
    {
        // A predictable initial password, not a random one: the admin is force-
        // walked through a change-password prompt on first login (must_change_password
        // below), so there is no standing benefit to unguessable entropy here — and
        // a fixed, memorable value is what operators actually hand new tenants.
        $password = $admin['password'] ?? 'password';

        $createdAdmin = DB::transaction(function () use ($tenant, $admin, $password, $entitlementPermissionNames): User {
            $this->grantEntitlements($tenant, $entitlementPermissionNames);

            return $this->context->runForTenant($tenant, function () use ($admin, $password): User {
                app(PermissionsAndRolesSeeder::class)->seedSystemRoles();
                app(LookupSeeder::class)->run();
                app(SystemCategorySeeder::class)->run();
                Branch::create(['name' => 'Main Branch', 'is_active' => true]);

                return $this->createAdmin($admin['name'], $admin['email'], $password);
            });
        });

        return ['tenant' => $tenant, 'owner' => $createdAdmin, 'password' => $password];
    }

    /**
     * Set the tenant's entitlement ceiling. Null grants every known permission.
     *
     * @param  array<int, string>|null  $permissionNames
     */
    public function grantEntitlements(Tenant $tenant, ?array $permissionNames = null): void
    {
        $ids = $permissionNames === null
            ? Permission::query()->pluck('id')->all()
            : Permission::query()->whereIn('name', $permissionNames)->pluck('id')->all();

        $tenant->entitlements()->sync($ids);
        $tenant->flushEntitlementCache();
    }

    private function createAdmin(string $name, string $email, string $password): User
    {
        $admin = User::create([
            'name' => $name,
            'email' => $email,
            'password' => Hash::make($password),
            'status' => User::STATUS_ACTIVE,
            'email_verified_at' => now(),
            'must_change_password' => true,
        ]);

        $role = Role::query()
            ->where('is_full_admin', true)
            ->where('is_active', true)
            ->firstOrFail();

        $admin->update(['role_id' => $role->id]);
        $admin->roles()->syncWithoutDetaching([$role->id]);
        $admin->flushPermissionCache();

        return $admin;
    }
}
