<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\AuditService;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Services\Authorization\AuthorizationDelegationService;
use App\Services\BranchAccessService;
use App\Services\SecureFileService;
use App\Services\UserBranchAssignmentService;
use App\Support\Authorization\PermissionName;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class UserController extends Controller
{
    public function __construct(
        private BranchAccessService $branchAccess,
        private UserBranchAssignmentService $branchAssignments,
        private AuthorizationAssignmentService $authorization,
        private AuthorizationDelegationService $delegation,
    ) {
    }

    public function index(Request $request): View
    {
        $query = User::with(['roles', 'branches'])->orderBy('name');
        if (! $this->branchAccess->isSystemWide($request->user())) {
            $branchIds = $this->branchAccess->accessibleBranchIds($request->user());
            if ($branchIds === []) {
                $query->whereRaw('1 = 0');
            } else {
                $query->where(function ($builder) use ($branchIds): void {
                    $builder->whereIn('primary_branch_id', $branchIds)
                        ->orWhereHas('branches', fn ($q) => $q->whereIn('branches.id', $branchIds));
                });
            }
        }

        if ($request->filled('search')) {
            $search = trim((string) $request->string('search'));
            $query->where(function ($builder) use ($search): void {
                $builder->where('name', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%")
                    ->orWhere('username', 'like', "%{$search}%")
                    ->orWhere('employee_number', 'like', "%{$search}%")
                    ->orWhere('position', 'like', "%{$search}%")
                    ->orWhere('phone', 'like', "%{$search}%");
            });
        }

        if ($request->filled('status')) {
            $query->where('status', $request->string('status'));
        }

        return view('admin.users.index', ['users' => $query->paginate(20)->withQueryString()]);
    }

    public function create(Request $request): View
    {
        return view('admin.users.form', [
            'staff' => new User(),
            'roles' => $this->delegation->assignableRoles($request->user()),
            'protectedRoles' => collect(),
            'branches' => $this->branchAccess->accessibleBranches($request->user()),
        ]);
    }

    public function store(Request $request, SecureFileService $files): RedirectResponse
    {
        $data = $this->normalizeAssignments($request, $this->validated($request));
        $this->assertAssignments($request, $data['branch_ids']);
        $this->delegation->assertRoleTransitionAllowed(
            $request->user(),
            null,
            $data['role_ids'],
            $data['status'],
        );

        $photo = $data['profile_photo'] ?? null;
        unset($data['profile_photo']);
        if ($photo) {
            $data['profile_photo_path'] = $files->store($photo, 'staff/profile-photos');
        }

        $user = DB::transaction(function () use ($data): User {
            $roleIds = $data['role_ids'];
            $branchIds = $data['branch_ids'];
            $primaryBranchId = (int) $data['primary_branch_id'];
            unset($data['role_ids'], $data['branch_ids']);

            $user = User::create($data);
            $this->authorization->syncUserRoleIds($user, $roleIds);
            $this->branchAssignments->synchronize($user, $primaryBranchId, $branchIds);

            return $user->fresh(['roles', 'branches']);
        });

        AuditService::record('staff.created', $user, [], $user->toArray(), $user->primary_branch_id);
        AuditService::record(
            'authorization.user.roles.assigned',
            $user,
            [],
            ['roles' => $user->roles->pluck('name')->sort()->values()->all()],
            $user->primary_branch_id,
        );

        return redirect()->route('admin.users.index')->with('success', 'Staff user created.');
    }

    public function edit(Request $request, User $user): View
    {
        $this->assertUserAccessible($request, $user);
        $this->assertProtectedAdministratorTarget($request, $user);

        return view('admin.users.form', [
            'staff' => $user->load(['roles', 'branches']),
            'roles' => $this->delegation->assignableRoles($request->user()),
            'protectedRoles' => $this->delegation->protectedRoles($request->user(), $user),
            'branches' => $this->branchAccess->accessibleBranches($request->user()),
        ]);
    }

    public function update(Request $request, User $user, SecureFileService $files): RedirectResponse
    {
        $this->assertUserAccessible($request, $user);
        $this->assertProtectedAdministratorTarget($request, $user);

        $user->load('roles');
        $before = $user->toArray();
        $beforeRoles = $user->roles->pluck('name')->sort()->values()->all();

        $data = $this->normalizeAssignments($request, $this->validated($request, $user), $user);
        $this->assertAssignments($request, $data['branch_ids']);
        $this->delegation->assertRoleTransitionAllowed(
            $request->user(),
            $user,
            $data['role_ids'],
            $data['status'],
        );

        $photo = $data['profile_photo'] ?? null;
        unset($data['profile_photo']);
        if ($photo) {
            $files->delete($user->profile_photo_path);
            $data['profile_photo_path'] = $files->store($photo, 'staff/profile-photos');
        }

        DB::transaction(function () use ($data, $user): void {
            $roleIds = $data['role_ids'];
            $branchIds = $data['branch_ids'];
            $primaryBranchId = (int) $data['primary_branch_id'];
            unset($data['role_ids'], $data['branch_ids']);
            if (blank($data['password'] ?? null)) {
                unset($data['password']);
            }

            $user->update($data);
            $this->authorization->syncUserRoleIds($user, $roleIds);
            $this->branchAssignments->synchronize($user, $primaryBranchId, $branchIds);
        });

        $fresh = $user->fresh(['roles', 'branches']);
        $afterRoles = $fresh->roles->pluck('name')->sort()->values()->all();

        AuditService::record('staff.updated', $fresh, $before, $fresh->toArray(), $fresh->primary_branch_id);
        if ($beforeRoles !== $afterRoles) {
            AuditService::record(
                'authorization.user.roles.updated',
                $fresh,
                ['roles' => $beforeRoles],
                ['roles' => $afterRoles],
                $fresh->primary_branch_id,
            );
        }

        return redirect()->route('admin.users.index')->with('success', 'Staff user updated.');
    }

    private function validated(Request $request, ?User $user = null): array
    {
        return $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'employee_number' => ['nullable', 'string', 'max:100', Rule::unique('users')->ignore($user?->id)],
            'email' => ['nullable', 'email', 'max:255', Rule::unique('users')->ignore($user?->id)],
            'username' => ['required', 'alpha_dash', 'max:100', Rule::unique('users')->ignore($user?->id)],
            'phone' => ['required', 'string', 'max:50'],
            'nin' => ['nullable', 'string', 'max:100'],
            'position' => ['required', 'string', 'max:150'],
            'profile_photo' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
            'status' => ['nullable', Rule::in(['active', 'inactive'])],
            'employed_at' => ['nullable', 'date'],
            'primary_branch_id' => ['required', 'exists:branches,id'],
            'branch_ids' => ['nullable', 'array'],
            'branch_ids.*' => ['integer', 'exists:branches,id'],
            'role_ids' => ['nullable', 'array'],
            'role_ids.*' => ['integer', 'exists:roles,id'],
            'password' => [$user ? 'nullable' : 'required', 'string', 'min:10'],
        ]);
    }

    private function normalizeAssignments(Request $request, array $data, ?User $user = null): array
    {
        $data['status'] = $data['status'] ?? $user?->status ?? 'active';
        $data['branch_ids'] = $this->branchAssignments->normalize(
            (int) $data['primary_branch_id'],
            $data['branch_ids'] ?? [],
        );
        $data['role_ids'] = $this->delegation->normalizeRequestedRoleIds(
            $request->user(),
            $user,
            $data['role_ids'] ?? [],
        );

        return $data;
    }

    private function assertAssignments(Request $request, array $branchIds): void
    {
        abort_unless(
            in_array((int) $request->integer('primary_branch_id'), array_map('intval', $branchIds), true),
            422,
            'Primary branch must be one of the assigned branches.',
        );

        if (! $this->branchAccess->isSystemWide($request->user())) {
            $this->branchAccess->assertBranchesAccessible($request->user(), $branchIds);
        }
    }

    private function assertUserAccessible(Request $request, User $user): void
    {
        if ($this->branchAccess->isSystemWide($request->user())) {
            return;
        }

        abort_unless(
            $this->branchAccess->sharesAnyBranch($request->user(), $user),
            403,
        );
    }

    private function assertProtectedAdministratorTarget(Request $request, User $user): void
    {
        if (! $this->delegation->isProtectedSuperAdministrator($user)) {
            return;
        }

        abort_unless(
            $request->user()->can(PermissionName::ROLES_ASSIGN_SUPER_ADMIN->value),
            403,
            'Only authorized super-administrator delegation may manage a Super Administrator account.',
        );
    }
}
