<?php

namespace Maestro\Core\Http\Controllers;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Maestro\Core\Support\AuthUserResolver;
use Maestro\Core\Support\PermissionGuardName;

class UserManagementController extends Controller
{
    /**
     * @return Builder
     */
    private function scopedQuery(Request $request): Builder
    {
        abort_unless($request->user(), 403);

        /** @var Builder $query */
        $query = AuthUserResolver::eloquentModelClass()::query();

        return $query->with(['roles:id,name']);
    }

    public function index(Request $request): JsonResponse
    {
        abort_unless($request->user()?->can('users.view'), 403);

        $users = $this->scopedQuery($request)
            ->orderBy('name')
            ->get()
            ->map(fn (Model $user) => $this->userPayload($user));

        return response()->json(['data' => $users]);
    }

    public function store(Request $request): JsonResponse
    {
        abort_unless($request->user()?->can('users.create'), 403);

        $guardName = PermissionGuardName::forApp();

        $validator = Validator::make($request->all(), [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'max:255', 'unique:users,email'],
            'password' => ['required', 'string', 'min:8'],
            'is_active' => ['boolean'],
            'roles' => ['nullable', 'array'],
            'roles.*' => ['string', Rule::exists('roles', 'name')->where('guard_name', $guardName)],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        $class = AuthUserResolver::eloquentModelClass();

        $user = $class::create([
            'name' => $request->input('name'),
            'email' => $request->input('email'),
            'password' => Hash::make($request->input('password')),
            'is_active' => $request->boolean('is_active', true),
        ]);

        if ($request->filled('roles') && $request->user()->can('users.assign-roles')) {
            $user->syncRoles($request->input('roles'));
            $user->load('roles:id,name');
            activity('users')
                ->causedBy($request->user())
                ->performedOn($user)
                ->withProperties([
                    'roles' => $user->roles->pluck('name')->values()->all(),
                ])
                ->event('updated')
                ->log('Assigned roles to user');
        }

        return response()->json([
            'data' => $this->userPayload($user->fresh('roles:id,name')),
        ], 201);
    }

    public function update(Request $request, Model $user): JsonResponse
    {
        abort_unless($request->user()?->can('users.edit'), 403);

        $guardName = PermissionGuardName::forApp();

        $validator = Validator::make($request->all(), [
            'name' => ['sometimes', 'string', 'max:255'],
            'email' => ['sometimes', 'email', 'max:255', 'unique:users,email,'.$user->id],
            'password' => ['nullable', 'string', 'min:8'],
            'is_active' => ['boolean'],
            'roles' => ['nullable', 'array'],
            'roles.*' => ['string', Rule::exists('roles', 'name')->where('guard_name', $guardName)],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        $data = $request->only(['name', 'email']);
        if ($request->has('is_active')) {
            $data['is_active'] = $request->boolean('is_active');
        }
        if ($request->filled('password')) {
            $data['password'] = Hash::make($request->input('password'));
        }

        if ($data !== []) {
            $user->update($data);
        }

        if ($request->has('roles') && $request->user()->can('users.assign-roles')) {
            $user->load('roles:id,name');
            $rolesBefore = $user->roles->pluck('name')->sort()->values()->all();
            $user->syncRoles($request->input('roles', []));
            $user->load('roles:id,name');
            $rolesAfter = $user->roles->pluck('name')->sort()->values()->all();

            if ($rolesBefore !== $rolesAfter) {
                activity('users')
                    ->causedBy($request->user())
                    ->performedOn($user)
                    ->withProperties([
                        'roles_before' => $rolesBefore,
                        'roles_after' => $rolesAfter,
                    ])
                    ->event('updated')
                    ->log('Updated user roles');
            }
        }

        return response()->json([
            'data' => $this->userPayload($user->fresh('roles:id,name')),
        ]);
    }

    public function destroy(Request $request, Model $user): JsonResponse
    {
        abort_unless($request->user()?->can('users.delete'), 403);
        abort_if($user->id === $request->user()->id, 422, 'You cannot delete your own account.');

        $user->delete();

        return response()->json(['message' => 'Deleted.']);
    }

    /**
     * @return array<string, mixed>
     */
    private function userPayload(Model $user): array
    {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'is_active' => (bool) ($user->is_active ?? true),
            'roles' => $user->roles->pluck('name')->values()->all(),
        ];
    }
}
