<?php

namespace App\Filament\Central\Resources\TenantResource\RelationManagers;

use App\Filament\Forms\Components\PermissionOverridePicker;
use App\Filament\Resources\UserResource;
use App\Models\Branch;
use App\Models\Role;
use App\Models\Sales\Till;
use App\Models\User;
use App\Services\AuditLog;
use App\Services\CurrentContext;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;

/**
 * Landlord-panel management of a tenant's staff (docs/migration-plan.md
 * Phase 12) — "who has access to this tenant", without impersonating just to
 * check. Mirrors the tenant-side UserResource's per-row surface (view / edit /
 * suspend / reactivate / deactivate / unlock / reset password / set PIN /
 * delete) plus a header "Create user" action for every user in the tenant.
 *
 * Every mutation lands in the tenant's audit trail flagged with
 * `via: master_control` plus the central operator's email (actor_id stays
 * null — the operator is not a tenant user), the same idiom
 * TenantResource::audit() uses.
 *
 * No per-model policy (see BranchesRelationManager) — but unlike Branches/
 * Tills, User is a tenant-scoped model whose own AuditLog::record() calls
 * need a bound tenant to write anything (see AuditLog::record()'s MySQL
 * NOT-NULL guard), so every mutating action here runs inside
 * CurrentContext::runForTenant(), the same idiom TenantResource::audit() uses.
 */
class UsersRelationManager extends RelationManager
{
    protected static string $relationship = 'users';

    protected static ?string $title = 'Users';

    protected static ?string $recordTitleAttribute = 'name';

    protected static bool $shouldSkipAuthorization = true;

    public function form(Schema $schema): Schema
    {
        return $schema->components([]);
    }

    public function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('name')->searchable()->sortable(),
                TextColumn::make('email')->searchable()->sortable(),
                TextColumn::make('phone')->placeholder('—'),
                TextColumn::make('status')
                    ->badge()
                    ->color(fn (string $state): string => match ($state) {
                        User::STATUS_ACTIVE => 'success',
                        User::STATUS_SUSPENDED => 'danger',
                        User::STATUS_INACTIVE => 'gray',
                        default => 'gray',
                    }),
                TextColumn::make('roles.name')->label('Roles')->badge()->placeholder('—'),
                IconColumn::make('locked')
                    ->label('Locked')
                    ->boolean()
                    ->state(fn (User $record): bool => $record->isLocked()),
                TextColumn::make('last_login_at')->dateTime()->placeholder('Never')->toggleable(),
            ])
            ->defaultSort('name')
            ->headerActions([
                Action::make('create')
                    ->label('Create user')
                    ->icon(Heroicon::OutlinedUserPlus)
                    ->modalHeading(fn (): string => 'Create user for '.$this->getOwnerRecord()->name)
                    ->schema([
                        Section::make('Profile')->schema([
                            TextInput::make('name')->required()->maxLength(120),
                            TextInput::make('email')
                                ->required()
                                ->email()
                                ->maxLength(255)
                                ->rule(fn (?User $record) => Rule::unique('users', 'email')->ignore($record?->id)),
                            TextInput::make('phone')->tel()->maxLength(20),
                            Select::make('status')
                                ->options(array_combine(User::STATUSES, User::STATUSES))
                                ->required()
                                ->default(User::STATUS_ACTIVE),
                            TextInput::make('password')
                                ->password()
                                ->revealable()
                                ->confirmed()
                                ->maxLength(128)
                                ->rule(Password::min(12)->mixedCase()->numbers()->uncompromised())
                                ->helperText('Leave blank to generate a strong one-time password, shown once after saving.'),
                            TextInput::make('password_confirmation')
                                ->password()
                                ->revealable()
                                ->dehydrated(false),
                        ])->columns(2),

                        Section::make('Roles')->schema([
                            Select::make('role_ids')
                                ->label('Roles')
                                ->multiple()
                                // Only this tenant's roles — the scoped
                                // picker must never leak another tenant's rows.
                                ->options(fn (): array => Role::query()
                                    ->where('tenant_id', $this->getOwnerRecord()->getKey())
                                    ->where('is_active', true)
                                    ->orderBy('name')
                                    ->pluck('name', 'id')
                                    ->all())
                                ->searchable()
                                ->preload()
                                ->live(),
                        ]),

                        Section::make('Permission overrides')
                            ->description('Exceptions for this user only. Everything else follows the roles above.')
                            ->schema([
                                PermissionOverridePicker::make('permission_overrides')
                                    ->label('Permissions')
                                    ->rolesStatePath('role_ids')
                                    ->tenantId(fn (): int => $this->getOwnerRecord()->getKey()),
                            ]),

                        Section::make('Access')->schema([
                            Toggle::make('is_all_branches')
                                ->label('All-Branch Access')
                                ->live(),
                            Select::make('warehouses')
                                ->label('Warehouses')
                                ->multiple()
                                ->options(fn (): array => Branch::query()
                                    ->where('tenant_id', $this->getOwnerRecord()->getKey())
                                    ->orderBy('name')
                                    ->pluck('name', 'id')
                                    ->all())
                                ->searchable()
                                ->preload()
                                ->visible(fn (Get $get): bool => ! $get('is_all_branches')),
                            Select::make('tills')
                                ->label('Tills')
                                ->multiple()
                                ->options(fn (): array => Till::query()
                                    ->where('tenant_id', $this->getOwnerRecord()->getKey())
                                    ->where('is_active', true)
                                    ->orderBy('name')
                                    ->pluck('name', 'id')
                                    ->all())
                                ->searchable()
                                ->preload(),
                        ])->columns(2),
                    ])
                    ->action(function (array $data): void {
                        $this->createUser($data);
                    }),
            ])
            ->recordActions([
                ViewAction::make()
                    ->schema([
                        Section::make()->schema([
                            TextEntry::make('name'),
                            TextEntry::make('email'),
                            TextEntry::make('phone')->placeholder('—'),
                            TextEntry::make('status')->badge(),
                            TextEntry::make('roles.name')->label('Roles')->badge()->placeholder('—'),
                            TextEntry::make('last_login_at')->dateTime()->placeholder('Never'),
                        ])->columns(2),
                    ]),

                Action::make('edit')
                    ->label('Edit')
                    ->icon(Heroicon::OutlinedPencilSquare)
                    ->schema([
                        Section::make('Profile')->schema([
                            TextInput::make('name')->required()->maxLength(120),
                            TextInput::make('email')
                                ->required()
                                ->email()
                                ->maxLength(255)
                                ->rule(fn (?User $record) => Rule::unique('users', 'email')->ignore($record?->id)),
                            TextInput::make('phone')->tel()->maxLength(20),
                            Select::make('status')
                                ->options(array_combine(User::STATUSES, User::STATUSES))
                                ->required(),
                            TextInput::make('password')
                                ->password()
                                ->revealable()
                                ->confirmed()
                                ->maxLength(128)
                                ->rule(Password::min(12)->mixedCase()->numbers()->uncompromised())
                                ->dehydrated(fn (?string $state): bool => filled($state))
                                ->helperText('Leave blank to keep the current password. Setting one forces a change at next login.'),
                            TextInput::make('password_confirmation')
                                ->password()
                                ->revealable()
                                ->dehydrated(false),
                        ])->columns(2),

                        Section::make('Roles')->schema([
                            Select::make('role_ids')
                                ->label('Roles')
                                ->multiple()
                                // Only this tenant's roles — the scoped
                                // picker must never leak another tenant's rows.
                                ->options(fn (): array => Role::query()
                                    ->where('tenant_id', $this->getOwnerRecord()->getKey())
                                    ->where('is_active', true)
                                    ->orderBy('name')
                                    ->pluck('name', 'id')
                                    ->all())
                                ->searchable()
                                ->preload()
                                // Live so the matrix below reshades to the new roles'
                                // grants immediately, matching UserResource's tenant-panel
                                // equivalent of this same field.
                                ->live(),
                        ]),

                        Section::make('Permission overrides')
                            ->description('Exceptions for this user only. Everything else follows the roles above.')
                            ->schema([
                                PermissionOverridePicker::make('permission_overrides')
                                    ->label('Permissions')
                                    ->rolesStatePath('role_ids')
                                    // Auth::user() here is a CentralAdmin, which has no
                                    // tenant_id of its own — cap the matrix at the
                                    // tenant actually being edited instead.
                                    ->tenantId(fn (): int => $this->getOwnerRecord()->getKey()),
                            ]),

                        Section::make('Access')->schema([
                            Toggle::make('is_all_branches')
                                ->label('All-Branch Access')
                                ->live(),
                            Select::make('warehouses')
                                ->label('Warehouses')
                                ->multiple()
                                ->options(fn (): array => Branch::query()
                                    ->where('tenant_id', $this->getOwnerRecord()->getKey())
                                    ->orderBy('name')
                                    ->pluck('name', 'id')
                                    ->all())
                                ->searchable()
                                ->preload()
                                ->visible(fn (Get $get): bool => ! $get('is_all_branches')),
                            Select::make('tills')
                                ->label('Tills')
                                ->multiple()
                                ->options(fn (): array => Till::query()
                                    ->where('tenant_id', $this->getOwnerRecord()->getKey())
                                    ->where('is_active', true)
                                    ->orderBy('name')
                                    ->pluck('name', 'id')
                                    ->all())
                                ->searchable()
                                ->preload(),
                        ])->columns(2),
                    ])
                    ->fillForm(fn (User $record): array => [
                        'role_ids' => $record->roles()->pluck('roles.id')->all(),
                        // The stored exception layer verbatim, not the effective
                        // set — see UserResource::applyRolesAndOverrides().
                        'permission_overrides' => $record->permissionOverrideLists(),
                    ])
                    ->action(function (User $record, array $data): void {
                        $this->updateRecord($record, $data);
                    }),

                Action::make('suspend')
                    ->color('danger')
                    ->requiresConfirmation()
                    ->visible(fn (User $record): bool => $record->status !== User::STATUS_SUSPENDED)
                    ->action(fn (User $record) => $this->setStatus($record, User::STATUS_SUSPENDED, 'user.suspended', 'User suspended.')),
                Action::make('reactivate')
                    ->color('success')
                    ->requiresConfirmation()
                    ->visible(fn (User $record): bool => $record->status !== User::STATUS_ACTIVE)
                    ->action(fn (User $record) => $this->setStatus($record, User::STATUS_ACTIVE, 'user.reactivated', 'User reactivated.')),
                Action::make('deactivate')
                    ->color('gray')
                    ->requiresConfirmation()
                    ->visible(fn (User $record): bool => $record->status !== User::STATUS_INACTIVE)
                    ->action(fn (User $record) => $this->setStatus($record, User::STATUS_INACTIVE, 'user.deactivated', 'User set to inactive.')),

                Action::make('unlock')
                    ->icon(Heroicon::OutlinedLockOpen)
                    ->requiresConfirmation()
                    ->visible(fn (User $record): bool => $record->isLocked())
                    ->action(fn (User $record) => $this->runForTenant(function () use ($record): void {
                        $record->forceFill(['locked_until' => null, 'failed_login_count' => 0])->save();
                        AuditLog::record('user.unlocked', $record, ['via' => 'master_control']);
                        Notification::make()->title('User unlocked.')->success()->send();
                    })),

                Action::make('resetPassword')
                    ->label('Reset password')
                    ->icon(Heroicon::OutlinedKey)
                    ->color('warning')
                    ->requiresConfirmation()
                    ->modalDescription('Generates a new one-time password and forces a change at next login.')
                    ->action(fn (User $record) => $this->runForTenant(function () use ($record): void {
                        $password = Str::password(14);

                        $record->forceFill([
                            'password' => Hash::make($password),
                            'failed_login_count' => 0,
                            'locked_until' => null,
                            'must_change_password' => true,
                        ])->save();

                        AuditLog::record('user.password_reset_by_admin', $record, ['via' => 'master_control']);

                        Notification::make()
                            ->title("Password for {$record->email} reset.")
                            ->body("New password: {$password}\n\nThe user must change it at next login. This is shown once.")
                            ->success()
                            ->persistent()
                            ->send();
                    })),

                Action::make('setPin')
                    ->label('Set PIN')
                    ->icon(Heroicon::OutlinedKey)
                    ->schema([
                        TextInput::make('pin')
                            ->label('PIN')
                            ->numeric()
                            ->rule('digits_between:4,6')
                            ->helperText('4–6 digits. Leave blank to clear the PIN.'),
                    ])
                    ->action(function (User $record, array $data): void {
                        $pin = filled($data['pin'] ?? null) ? (string) $data['pin'] : null;

                        $this->runForTenant(function () use ($record, $pin): void {
                            $record->forceFill(['pin_hash' => $pin !== null ? Hash::make($pin) : null])->save();
                            AuditLog::record($pin !== null ? 'user.pin_set' : 'user.pin_cleared', $record, ['via' => 'master_control']);
                        });

                        Notification::make()->title($pin !== null ? 'PIN set.' : 'PIN cleared.')->success()->send();
                    }),

                DeleteAction::make()
                    ->before(function (User $record): void {
                        $this->runForTenant(function () use ($record): void {
                            $record->flushPermissionCache();
                            AuditLog::record('user.deleted', $record, [
                                'name' => $record->name,
                                'email' => $record->email,
                                'via' => 'master_control',
                            ]);
                        });
                    }),
            ]);
    }

    /**
     * Ported from CreateUser (mutateFormDataBeforeCreate + afterCreate): create
     * the account inside the tenant's scope, sync roles / warehouses / tills,
     * store the per-user permission overrides, then audit into the tenant's log
     * with `via: master_control`. A blank password auto-generates a strong
     * one-time password shown exactly once — the same convention as the
     * reset-password action — and every admin-set credential forces a change
     * at next login. The override pivots carry granted_by = null because the
     * actor is a central admin, not a tenant user (see
     * UserResource::applyRolesAndOverrides()).
     */
    private function createUser(array $data): void
    {
        $password = $data['password'] ?? null;
        unset($data['password'], $data['password_confirmation']);

        $roleIds = array_values(array_unique(array_map('intval', $data['role_ids'] ?? [])));
        $overrides = PermissionOverridePicker::normalize($data['permission_overrides'] ?? null);
        $warehouseIds = array_map('intval', $data['warehouses'] ?? []);
        $tillIds = array_map('intval', $data['tills'] ?? []);
        unset($data['role_ids'], $data['permission_overrides'], $data['warehouses'], $data['tills']);

        $generated = ! filled($password);
        $password ??= Str::password(14);

        $data['role_id'] = $roleIds[0] ?? null;
        $data['password'] = $password;
        $data['must_change_password'] = true;
        $data['email_verified_at'] = now();

        $user = null;

        $this->runForTenant(function () use ($data, $roleIds, $overrides, $warehouseIds, $tillIds, &$user): void {
            $user = User::create($data);
            $user->warehouses()->sync($warehouseIds);
            $user->tills()->sync($tillIds);

            $applied = UserResource::applyRolesAndOverrides($user, $roleIds, $overrides, null);

            AuditLog::record('user.created', $user, [
                'roles' => $user->roles()->pluck('name')->all(),
                'allow_overrides' => $applied['allow'],
                'deny_overrides' => $applied['deny'],
                'via' => 'master_control',
            ]);
        });

        if ($generated) {
            Notification::make()
                ->title("User {$user->email} created.")
                ->body("Temporary password: {$password}\n\nThe user must change it at next login. This is shown once.")
                ->success()
                ->persistent()
                ->send();
        } else {
            Notification::make()->title('User created.')->success()->send();
        }
    }

    /**
     * Ported from EditUser (mutateFormDataBeforeSave + afterSave): update the
     * profile, sync roles / warehouses / tills, store the per-user permission
     * overrides, then audit the exact changed fields into the tenant's log with
     * `via: master_control`. The override pivots carry granted_by = null because
     * the actor is a central admin, not a tenant user (see
     * UserResource::applyRolesAndOverrides()).
     */
    private function updateRecord(User $record, array $data): void
    {
        $original = $record->only(['name', 'email', 'phone', 'status', 'is_all_branches']);
        $oldRoleNames = $record->roles()->pluck('name')->all();
        $oldTillIds = $record->tills()->pluck('tills.id')->sort()->values()->all();

        $roleIds = array_values(array_unique(array_map('intval', $data['role_ids'] ?? [])));
        $overrides = PermissionOverridePicker::normalize($data['permission_overrides'] ?? null);
        $warehouseIds = array_map('intval', $data['warehouses'] ?? []);
        $tillIds = array_map('intval', $data['tills'] ?? []);
        unset($data['role_ids'], $data['permission_overrides'], $data['warehouses'], $data['tills']);

        $data['role_id'] = $roleIds[0] ?? null;

        if (filled($data['password'] ?? null)) {
            // Admin-set credential: force a change at next login.
            $data['must_change_password'] = true;
        } else {
            unset($data['password']);
        }
        unset($data['password_confirmation']);

        $this->runForTenant(function () use ($record, $data, $roleIds, $overrides, $warehouseIds, $tillIds, $original, $oldRoleNames, $oldTillIds): void {
            $record->update($data);
            $record->warehouses()->sync($warehouseIds);
            $record->tills()->sync($tillIds);

            $changes = [];
            foreach ($original as $field => $oldValue) {
                $newValue = $record->{$field};
                if ($oldValue !== $newValue) {
                    $changes[$field] = ['from' => $oldValue, 'to' => $newValue];
                }
            }

            $newTillIds = $record->tills()->pluck('tills.id')->sort()->values()->all();
            if ($oldTillIds !== $newTillIds) {
                $changes['till_ids'] = ['from' => $oldTillIds, 'to' => $newTillIds];
            }

            // applyRolesAndOverrides() syncs roles, writes the override pivots
            // verbatim and (via $grantorId = null) leaves granted_by untouched.
            $applied = UserResource::applyRolesAndOverrides($record, $roleIds, $overrides, null);

            $newRoleNames = $record->roles()->pluck('name')->all();
            if ($oldRoleNames !== $newRoleNames) {
                $changes['roles'] = ['from' => $oldRoleNames, 'to' => $newRoleNames];
            }

            AuditLog::record('user.updated', $record, [
                'changes' => $changes,
                'allow_overrides' => $applied['allow'],
                'deny_overrides' => $applied['deny'],
                'via' => 'master_control',
            ]);
        });

        Notification::make()->title('User updated.')->success()->send();
    }

    private function setStatus(User $record, string $status, string $auditAction, string $successMessage): void
    {
        $this->runForTenant(function () use ($record, $status, $auditAction): void {
            $record->update(['status' => $status]);
            $record->flushPermissionCache();
            AuditLog::record($auditAction, $record, ['via' => 'master_control']);
        });

        Notification::make()->title($successMessage)->success()->send();
    }

    /**
     * AuditLog::record() (and any tenant-scoped write) needs a bound tenant —
     * this panel has none by default (see class docblock).
     */
    private function runForTenant(\Closure $callback): void
    {
        app(CurrentContext::class)->runForTenant($this->getOwnerRecord(), $callback);
    }
}
