<?php

namespace App\Filament\Resources;

use App\Filament\Resources\Concerns\ChecksPermissionEscalation;
use App\Filament\Resources\Concerns\HasStandardTableFeatures;
use App\Filament\Resources\RoleResource\Pages\CreateRole;
use App\Filament\Resources\RoleResource\Pages\EditRole;
use App\Filament\Resources\RoleResource\Pages\ListRoles;
use App\Filament\Resources\RoleResource\Pages\ViewRole;
use App\Filament\Support\Perm;
use App\Filament\Support\PermissionMatrix;
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use App\Services\AuditLog;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;

/**
 * Ported from App\Http\Controllers\UserManagement\RoleController
 * (docs/migration-plan.md §4 flagged this gap: "UserPolicy and RolePolicy
 * port verbatim ... and are used for those two Resources only" — this is the
 * Resource that claim was written for, never built until now).
 *
 * Deliberately does NOT use HasModulePermissions — per §4, this Resource
 * defers to the already-ported, already-registered RolePolicy (Gate::policy()
 * in AppServiceProvider) for its record-level rules (a full-admin role can
 * only be edited/duplicated by a full admin; a role can't be deleted while
 * is_system or while it still has members), which HasModulePermissions'
 * blanket module-key check cannot express. Filament's default canX() methods
 * already resolve through the Gate when a Resource declares none of its own.
 */
class RoleResource extends Resource
{
    use ChecksPermissionEscalation;
    use HasStandardTableFeatures;

    protected static ?string $model = Role::class;

    protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedShieldCheck;

    protected static string|\UnitEnum|null $navigationGroup = 'Administration';

    protected static ?int $navigationSort = 10;

    protected static ?string $recordTitleAttribute = 'name';

    /**
     * Same reasoning as TillResource's override: `user_management_roles` has no
     * `export` action of its own, so there is no `.export` permission for the
     * trait's default to require — and this Resource declares no
     * $permissionModuleKey at all, by the design noted above. Deferring to
     * canViewAny() keeps the gate where the rest of this Resource's rules
     * already live (RolePolicy::viewAny() => user_management_roles.access).
     */
    public static function canExport(): bool
    {
        return static::canViewAny();
    }

    public static function form(Schema $schema): Schema
    {
        return $schema->components([
            Section::make('Role')->schema([
                TextInput::make('name')
                    ->required()
                    ->maxLength(120)
                    ->rule(fn (?Role $record) => Rule::unique('roles', 'name')->ignore($record?->id)),
                Textarea::make('description')->maxLength(255)->columnSpanFull(),
                Toggle::make('is_active')
                    ->default(true)
                    // Ported rule: an is_full_admin role is always active —
                    // RoleController::update() forces true regardless of input.
                    ->disabled(fn (?Role $record): bool => (bool) $record?->is_full_admin)
                    ->dehydrateStateUsing(fn (?Role $record, $state) => $record?->is_full_admin ? true : $state),
                Toggle::make('is_all_branches')
                    ->label('All-Branch Access (role-wide)')
                    ->helperText('Every active holder of this role sees every branch, in addition to whatever is separately granted at the user level.')
                    ->rule(static::allBranchAccessEscalationRule()),
            ])->columns(2),

            Section::make('Permissions')
                ->description('A full-admin role bypasses every permission check, so its grant list is not editable here.')
                ->visible(fn (?Role $record): bool => ! $record?->is_full_admin)
                ->schema([
                    PermissionMatrix::checkboxList('permissions')
                        ->limitedToTenantEntitlement()
                        ->rule(static::permissionEscalationRule()),
                ]),
        ]);
    }

    public static function table(Table $table): Table
    {
        return static::standardTableDefaults($table)
            ->recordUrl(static::standardRecordUrl())
            ->emptyStateHeading('No roles yet')
            ->emptyStateDescription('Roles bundle permissions for the users who hold them.')
            ->modifyQueryUsing(fn ($query) => $query->withCount(['users', 'permissions']))
            ->columns([
                TextColumn::make('name')->searchable()->sortable(),
                TextColumn::make('description')->placeholder('—')->limit(60),
                IconColumn::make('is_system')->boolean()->label('System'),
                IconColumn::make('is_full_admin')->boolean()->label('Full admin'),
                IconColumn::make('is_active')->boolean(),
                TextColumn::make('users_count')->label('Members'),
                TextColumn::make('permissions_count')->label('Permissions'),
                TextColumn::make('updated_at')->dateTime()->sortable()->toggleable(),
            ])
            // Matches RoleController::index()'s orderByDesc('is_active')->orderBy('name').
            ->defaultSort('is_active', 'desc')
            ->filters([
                TernaryFilter::make('is_active')->label('Active'),
            ])
            ->recordActions([
                Action::make('view')
                    ->url(fn (Role $record): string => ViewRole::getUrl(['record' => $record]))
                    ->visible(fn (Role $record): bool => static::canView($record)),
                EditAction::make(),
                Action::make('duplicate')
                    ->icon(Heroicon::OutlinedDocumentDuplicate)
                    ->visible(fn (Role $record): bool => Perm::can('user_management_roles', 'duplicate')
                        && (! $record->is_full_admin || (Auth::user()?->isFullAdmin() ?? false)))
                    ->action(fn (Role $record) => static::duplicate($record)),
                Action::make('toggleActive')
                    ->label(fn (Role $record): string => $record->is_active ? 'Deactivate' : 'Activate')
                    ->color(fn (Role $record): string => $record->is_active ? 'danger' : 'success')
                    ->requiresConfirmation()
                    ->visible(fn (Role $record): bool => Perm::can('user_management_roles', 'toggle_active') && ! $record->is_full_admin)
                    ->action(fn (Role $record) => static::toggleActive($record)),
                DeleteAction::make()
                    ->before(function (Role $record): void {
                        AuditLog::record('role.deleted', $record, ['name' => $record->name]);
                    }),
            ]);
    }

    /**
     * Ported from RoleController::duplicate().
     */
    public static function duplicate(Role $role): void
    {
        $copy = $role->replicate(['is_system']);

        // The list query's withCount(['users', 'permissions']) attaches
        // computed users_count/permissions_count attributes that replicate()
        // would otherwise persist as real columns.
        unset($copy->users_count, $copy->permissions_count);

        $copy->name = static::uniqueCopyName($role->name);
        $copy->is_system = false;
        $copy->is_full_admin = false;
        // Mirrors is_full_admin: duplicating an elevated role must not silently
        // mint another one — granting All-Branch Access is a deliberate act,
        // not a side effect of "duplicate", and the duplicate action itself
        // isn't gated by allBranchAccessEscalationRule().
        $copy->is_all_branches = false;
        $copy->created_by = Auth::id();
        $copy->updated_by = Auth::id();
        $copy->save();

        $copy->permissions()->sync($role->permissions()->pluck('permissions.id')->all());

        AuditLog::record('role.duplicated', $copy, [
            'name' => $copy->name,
            'source' => $role->name,
            'source_role_id' => $role->id,
        ]);

        Notification::make()->title("Role duplicated as \"{$copy->name}\".")->success()->send();
    }

    public static function uniqueCopyName(string $baseName): string
    {
        $candidate = "Copy of {$baseName}";
        $n = 1;
        while (Role::query()->where('name', $candidate)->exists()) {
            $n++;
            $candidate = "Copy of {$baseName} ({$n})";
        }

        return $candidate;
    }

    /**
     * Ported from RoleController::toggleActive(): flip is_active, then flush
     * every member's permission cache — activating/deactivating a role
     * changes every holder's effective permissions immediately.
     */
    public static function toggleActive(Role $role): void
    {
        $role->update(['is_active' => ! $role->is_active, 'updated_by' => Auth::id()]);

        static::flushRoleMembers($role);

        AuditLog::record('role.toggled_active', $role, ['is_active' => $role->is_active]);

        Notification::make()->title($role->is_active ? 'Role activated.' : 'Role deactivated.')->success()->send();
    }

    /**
     * Ported from RoleController::flushRoleMembers(): invalidate the cache for
     * every user holding this role via EITHER the legacy singular `role_id`
     * column (Role::users(), a HasMany) OR the many-to-many `user_roles`
     * pivot — the source deliberately checks both, and so does this.
     */
    public static function flushRoleMembers(Role $role): void
    {
        $role->users()->get()->each(fn (User $user) => $user->flushPermissionCache());

        User::query()
            ->whereHas('roles', fn ($q) => $q->where('roles.id', $role->id))
            ->get()
            ->each(fn (User $user) => $user->flushPermissionCache());
    }

    /**
     * @param  array<int, string>  $permissionNames
     * @return array<int, int>
     */
    public static function permissionIdsFor(array $permissionNames): array
    {
        return Permission::query()->whereIn('name', $permissionNames)->pluck('id')->all();
    }

    public static function getPages(): array
    {
        return [
            'index' => ListRoles::route('/'),
            'create' => CreateRole::route('/create'),
            'view' => ViewRole::route('/{record}'),
            'edit' => EditRole::route('/{record}/edit'),
        ];
    }
}
