<?php

declare(strict_types=1);

namespace App\Filament\Resources\DberpAdminRoles;

use App\Enums\AdminRoleStatus;
use App\Filament\Concerns\InteractsWithDberpPermissions;
use App\Filament\Resources\DberpAdminRoles\Pages\CreateDberpAdminRole;
use App\Filament\Resources\DberpAdminRoles\Pages\EditDberpAdminRole;
use App\Filament\Resources\DberpAdminRoles\Pages\ListDberpAdminRoles;
use App\Models\DberpAdminRole;
use App\Models\User;
use App\Services\DberpAdminAccessService;
use App\Services\DberpPermissionRegistry;
use App\Services\DberpPermissionService;
use BackedEnum;
use DomainException;
use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use UnitEnum;

class DberpAdminRoleResource extends Resource
{
    use InteractsWithDberpPermissions;

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

    protected static ?string $recordTitleAttribute = 'display_name';

    protected static ?int $navigationSort = 60;

    public static function form(Schema $schema): Schema
    {
        $components = [
            Section::make(__('dberp.sections.admin_role_basic'))
                ->schema([
                    TextInput::make('display_name')
                        ->label(__('dberp.fields.role_name'))
                        ->required()
                        ->maxLength(120),
                    Select::make('status')
                        ->label(__('dberp.fields.status'))
                        ->options(AdminRoleStatus::options())
                        ->default(AdminRoleStatus::Enabled->value)
                        ->required()
                        ->disabled(fn (?DberpAdminRole $record): bool => $record instanceof DberpAdminRole && ! static::canToggleStatus($record)),
                    TextInput::make('sort_order')
                        ->label(__('dberp.fields.role_list_order'))
                        ->numeric()
                        ->default(100)
                        ->required(),
                    Textarea::make('description')
                        ->label(__('dberp.fields.role_description'))
                        ->rows(3)
                        ->columnSpanFull(),
                ])
                ->columns(2),
        ];

        if (! static::isIntegrationNavigationEntryEnabled()) {
            $components[] = Section::make(__('dberp.sections.admin_role_integration_notice'))
                ->description(__('dberp.hints.admin_role_integration_nav_hidden'))
                ->schema([])
                ->columnSpanFull();
        }

        $components[] = Section::make(__('dberp.sections.admin_role_permissions_intro'))
            ->description(__('dberp.sections.admin_role_permissions'))
            ->schema([])
            ->columnSpanFull();

        return $schema
            ->components([
                ...$components,
                ...static::permissionSections(),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('sort_order', 'asc')
            ->columns([
                TextColumn::make('display_name')
                    ->label(__('dberp.fields.role_name'))
                    ->searchable(),
                TextColumn::make('description')
                    ->label(__('dberp.fields.role_description'))
                    ->placeholder('-')
                    ->wrap(),
                TextColumn::make('sort_order')
                    ->label(__('dberp.fields.role_list_order'))
                    ->sortable(),
                TextColumn::make('users_count')
                    ->label(__('dberp.fields.bound_admin_count'))
                    ->sortable(),
                TextColumn::make('permissions_count')
                    ->label(__('dberp.fields.permission_count'))
                    ->sortable(),
                TextColumn::make('status')
                    ->label(__('dberp.fields.status'))
                    ->badge()
                    ->state(fn (DberpAdminRole $record): string => $record->status?->label() ?? AdminRoleStatus::Enabled->label())
                    ->color(fn (DberpAdminRole $record): string => $record->isEnabled() ? 'success' : 'gray'),
                TextColumn::make('created_at')
                    ->label(__('dberp.fields.created_at'))
                    ->dateTime()
                    ->sortable(),
            ])
            ->filters([
                SelectFilter::make('status')
                    ->label(__('dberp.fields.status'))
                    ->options(AdminRoleStatus::options()),
                Filter::make('keyword')
                    ->schema([
                        TextInput::make('value')->label(__('dberp.fields.keyword')),
                    ])
                    ->query(function (Builder $query, array $data): Builder {
                        $keyword = trim((string) ($data['value'] ?? ''));

                        if ($keyword === '') {
                            return $query;
                        }

                        return $query->where(function (Builder $builder) use ($keyword): void {
                            $builder
                                ->where('display_name', 'like', "%{$keyword}%")
                                ->orWhere('description', 'like', "%{$keyword}%");
                        });
                    }),
            ])
            ->recordActions([
                EditAction::make()
                    ->label(__('dberp.actions.edit'))
                    ->visible(fn (DberpAdminRole $record): bool => static::canEdit($record)),
                Action::make('copy_role')
                    ->label(__('dberp.actions.copy_role'))
                    ->color('gray')
                    ->visible(fn (DberpAdminRole $record): bool => static::canCopy($record))
                    ->modalDescription((string) __('dberp.hints.admin_role_copy'))
                    ->form([
                        TextInput::make('display_name')
                            ->label(__('dberp.fields.role_name'))
                            ->required()
                            ->maxLength(120),
                        Textarea::make('description')
                            ->label(__('dberp.fields.role_description'))
                            ->rows(3),
                        TextInput::make('sort_order')
                            ->label(__('dberp.fields.role_list_order'))
                            ->numeric()
                            ->default(fn (DberpAdminRole $record): int => (int) $record->sort_order),
                        Select::make('status')
                            ->label(__('dberp.fields.status'))
                            ->options(AdminRoleStatus::options())
                            ->default(AdminRoleStatus::Enabled->value)
                            ->required(),
                    ])
                    ->fillForm(fn (DberpAdminRole $record): array => [
                        'display_name' => $record->display_name.(string) __('dberp.fields.role_display_name_copy_suffix'),
                        'description' => $record->description,
                        'sort_order' => $record->sort_order,
                        'status' => AdminRoleStatus::Enabled->value,
                    ])
                    ->action(function (DberpAdminRole $record, array $data): void {
                        try {
                            $role = app(DberpAdminAccessService::class)->copyRole(
                                $record,
                                $data,
                                Auth::user(),
                                (string) __('dberp.messages.audit_remark_admin_role_copied_from_resource'),
                            );

                            Notification::make()
                                ->title((string) __('dberp.notifications.completed'))
                                ->body((string) __('dberp.actions.copy_role'))
                                ->success()
                                ->send();

                            redirect(static::getUrl('edit', ['record' => $role]));
                        } catch (AuthorizationException|DomainException $exception) {
                            Notification::make()
                                ->title($exception->getMessage())
                                ->danger()
                                ->send();
                        }
                    }),
                Action::make('toggle_status')
                    ->label(fn (DberpAdminRole $record): string => $record->isEnabled() ? (string) __('dberp.actions.disable') : (string) __('dberp.actions.enable'))
                    ->color(fn (DberpAdminRole $record): string => $record->isEnabled() ? 'danger' : 'success')
                    ->visible(fn (DberpAdminRole $record): bool => static::canToggleStatus($record))
                    ->requiresConfirmation()
                    ->modalDescription(fn (DberpAdminRole $record): string => $record->isEnabled() ? (string) __('dberp.hints.admin_role_disable') : (string) __('dberp.hints.admin_role_enable'))
                    ->action(function (DberpAdminRole $record): void {
                        try {
                            $status = $record->isEnabled()
                                ? AdminRoleStatus::Disabled
                                : AdminRoleStatus::Enabled;

                            app(DberpAdminAccessService::class)->updateRoleStatus(
                                $record,
                                $status,
                                Auth::user(),
                                (string) __('dberp.messages.audit_remark_admin_role_status_toggled_from_resource'),
                            );

                            Notification::make()
                                ->title((string) __('dberp.notifications.completed'))
                                ->body($status->label())
                                ->success()
                                ->send();
                        } catch (AuthorizationException|DomainException $exception) {
                            Notification::make()
                                ->title($exception->getMessage())
                                ->danger()
                                ->send();
                        }
                    }),
                Action::make('delete_role')
                    ->label(__('dberp.actions.delete'))
                    ->color('danger')
                    ->visible(fn (DberpAdminRole $record): bool => static::canDelete($record))
                    ->requiresConfirmation()
                    ->modalDescription((string) __('dberp.hints.admin_role_delete'))
                    ->action(function (DberpAdminRole $record): void {
                        try {
                            app(DberpAdminAccessService::class)->deleteRole(
                                $record,
                                Auth::user(),
                                (string) __('dberp.messages.audit_remark_admin_role_deleted_from_resource'),
                            );

                            Notification::make()
                                ->title((string) __('dberp.notifications.completed'))
                                ->body((string) __('dberp.actions.delete'))
                                ->success()
                                ->send();
                        } catch (AuthorizationException|DomainException $exception) {
                            Notification::make()
                                ->title($exception->getMessage())
                                ->danger()
                                ->send();
                        }
                    }),
            ]);
    }

    public static function getEloquentQuery(): Builder
    {
        return parent::getEloquentQuery()
            ->withCount(['users', 'permissions'])
            ->orderBy('sort_order')
            ->orderBy('id');
    }

    public static function getPages(): array
    {
        return [
            'index' => ListDberpAdminRoles::route('/'),
            'create' => CreateDberpAdminRole::route('/create'),
            'edit' => EditDberpAdminRole::route('/{record}/edit'),
        ];
    }

    public static function canViewAny(): bool
    {
        return static::allowsAnyPermission([
            DberpPermissionService::VIEW_ADMIN_ROLE,
            DberpPermissionService::CREATE_ADMIN_ROLE,
            DberpPermissionService::EDIT_ADMIN_ROLE,
            DberpPermissionService::DISABLE_ADMIN_ROLE,
            DberpPermissionService::COPY_ADMIN_ROLE,
            DberpPermissionService::DELETE_ADMIN_ROLE,
            DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS,
        ]);
    }

    public static function canCreate(): bool
    {
        return static::allowsPermission(DberpPermissionService::CREATE_ADMIN_ROLE);
    }

    public static function canEdit(Model $record): bool
    {
        if (! $record instanceof DberpAdminRole) {
            return false;
        }

        $currentUser = Auth::user();

        if (! $currentUser instanceof User) {
            return false;
        }

        if ($record->is_super_admin && ! $currentUser->isSuperAdmin()) {
            return false;
        }

        return static::allowsPermission(DberpPermissionService::EDIT_ADMIN_ROLE)
            || static::allowsPermission(DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS)
            || static::allowsPermission(DberpPermissionService::DISABLE_ADMIN_ROLE);
    }

    public static function canDelete(Model $record): bool
    {
        if (! $record instanceof DberpAdminRole) {
            return false;
        }

        $currentUser = Auth::user();

        if (! $currentUser instanceof User) {
            return false;
        }

        if ($record->is_super_admin && ! $currentUser->isSuperAdmin()) {
            return false;
        }

        if ($record->isSystemPreset()) {
            return false;
        }

        return static::allowsPermission(DberpPermissionService::DELETE_ADMIN_ROLE);
    }

    public static function getNavigationGroup(): string|UnitEnum|null
    {
        return __('dberp.nav.system');
    }

    public static function getNavigationIcon(): string|BackedEnum|Htmlable|null
    {
        return null;
    }

    public static function getModelLabel(): string
    {
        return __('dberp.resources.admin_role.label');
    }

    public static function getPluralModelLabel(): string
    {
        return __('dberp.resources.admin_role.plural');
    }

    public static function isIntegrationNavigationEntryEnabled(): bool
    {
        return (bool) config('dberp.integration.nav_enabled', false);
    }

    /**
     * @return list<string>
     */
    public static function integrationPermissionCodes(): array
    {
        $grouped = app(DberpPermissionRegistry::class)->groupedRecordsByNavigation();
        $integration = $grouped['integration'] ?? null;

        if (! is_array($integration) || ! isset($integration['permissions']) || ! is_array($integration['permissions'])) {
            return [];
        }

        return array_keys($integration['permissions']);
    }

    /**
     * 侧栏关闭联通入口时，角色表单不展示联通分组；保存时须把角色上已有的联通权限并回提交结果，避免误删。
     *
     * @param  list<string>  $permissionCodes
     * @return list<string>
     */
    public static function mergeRoleIntegrationPermissionSelections(DberpAdminRole $role, array $permissionCodes): array
    {
        if (static::isIntegrationNavigationEntryEnabled()) {
            return $permissionCodes;
        }

        $integrationCodes = static::integrationPermissionCodes();

        if ($integrationCodes === []) {
            return $permissionCodes;
        }

        $fromRole = $role->permissions()
            ->whereIn('core_permissions.name', $integrationCodes)
            ->pluck('core_permissions.name')
            ->map(fn (mixed $name): string => (string) $name)
            ->all();

        if ($fromRole === []) {
            return $permissionCodes;
        }

        return array_values(array_unique(array_merge($permissionCodes, $fromRole)));
    }

    /**
     * @return list<Section>
     */
    public static function permissionSections(): array
    {
        $grouped = collect(app(DberpPermissionRegistry::class)->groupedRecordsByNavigation());

        if (! static::isIntegrationNavigationEntryEnabled()) {
            $grouped = $grouped->except(['integration']);
        }

        $registry = app(DberpPermissionRegistry::class);

        return $grouped
            ->map(function (array $group, string $navigationGroup) use ($registry): Section {
                $options = collect($group['permissions'])
                    ->mapWithKeys(fn (array $definition, string $code): array => [
                        $code => $registry->localizedDisplayName($code, $definition['display_name']),
                    ])
                    ->all();

                $descriptions = collect($group['permissions'])
                    ->mapWithKeys(function (array $definition, string $code): array {
                        $labelBase = trim((string) ($definition['display_name'] ?? ''));
                        $description = trim((string) ($definition['description'] ?? ''));

                        if ($description === '' || $description === $labelBase) {
                            return [$code => ''];
                        }

                        return [$code => $description];
                    })
                    ->all();

                $checkboxList = CheckboxList::make("permission_groups.{$navigationGroup}")
                    ->label(__('dberp.fields.permission_configuration'))
                    ->options($options)
                    ->searchable()
                    ->searchPrompt(__('dberp.hints.admin_role_permission_search_prompt'))
                    ->columns(2)
                    ->gridDirection('row')
                    ->bulkToggleable()
                    ->columnSpanFull()
                    ->disabled(fn (?DberpAdminRole $record): bool => ! static::canManagePermissionSelections($record))
                    ->helperText(fn (?DberpAdminRole $record): ?string => static::permissionSectionHint($record));

                if (collect($descriptions)->contains(fn (mixed $text): bool => is_string($text) && $text !== '')) {
                    $checkboxList->descriptions($descriptions);
                }

                return Section::make($group['label'])
                    ->heading($group['label'])
                    ->schema([
                        $checkboxList,
                    ])
                    ->collapsible()
                    ->collapsed();
            })
            ->values()
            ->all();
    }

    /**
     * @param  array<string, mixed>  $data
     * @return list<string>
     */
    public static function extractPermissionCodes(array $data): array
    {
        return collect((array) ($data['permission_groups'] ?? []))
            ->flatMap(fn (mixed $codes): array => array_values(array_filter((array) $codes)))
            ->map(fn (mixed $code): string => (string) $code)
            ->filter(fn (string $code): bool => $code !== '')
            ->unique()
            ->values()
            ->all();
    }

    /**
     * @param  list<string>  $permissionCodes
     * @return array<string, list<string>>
     */
    public static function permissionGroupsForForm(array $permissionCodes): array
    {
        $grouped = collect(app(DberpPermissionRegistry::class)->groupedRecordsByNavigation());

        if (! static::isIntegrationNavigationEntryEnabled()) {
            $grouped = $grouped->except(['integration']);
        }

        return $grouped
            ->mapWithKeys(function (array $group, string $navigationGroup) use ($permissionCodes): array {
                return [
                    $navigationGroup => array_values(array_intersect(array_keys($group['permissions']), $permissionCodes)),
                ];
            })
            ->all();
    }

    private static function canCopy(DberpAdminRole $record): bool
    {
        $currentUser = Auth::user();

        if (! $currentUser instanceof User) {
            return false;
        }

        if ($record->is_super_admin && ! $currentUser->isSuperAdmin()) {
            return false;
        }

        return static::allowsPermission(DberpPermissionService::CREATE_ADMIN_ROLE)
            && static::allowsPermission(DberpPermissionService::COPY_ADMIN_ROLE)
            && static::allowsPermission(DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS);
    }

    private static function canToggleStatus(DberpAdminRole $record): bool
    {
        $currentUser = Auth::user();

        if (! $currentUser instanceof User) {
            return false;
        }

        if ($record->is_super_admin && ! $currentUser->isSuperAdmin()) {
            return false;
        }

        if ($record->isSystemPreset() && $record->isEnabled()) {
            return false;
        }

        return static::allowsPermission(DberpPermissionService::DISABLE_ADMIN_ROLE);
    }

    private static function canManagePermissionSelections(?DberpAdminRole $record): bool
    {
        $currentUser = Auth::user();

        if (! $currentUser instanceof User) {
            return false;
        }

        if ($record instanceof DberpAdminRole && $record->is_super_admin) {
            return false;
        }

        return static::allowsPermission(DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS);
    }

    private static function permissionSectionHint(?DberpAdminRole $record): ?string
    {
        if ($record instanceof DberpAdminRole && $record->is_super_admin) {
            return (string) __('dberp.hints.admin_role_super_admin_permissions');
        }

        if (! static::canManagePermissionSelections($record)) {
            return (string) __('dberp.hints.admin_role_permission_readonly');
        }

        return null;
    }
}
