<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Role;
use App\Services\AuditService;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Services\Authorization\AuthorizationDelegationService;
use App\Support\Authorization\PermissionCatalog;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class RoleController extends Controller
{
    public function __construct(
        private AuthorizationAssignmentService $authorization,
        private AuthorizationDelegationService $delegation,
    ) {
    }

    public function index(Request $request): View
    {
        $query = Role::withCount(['permissions', 'users'])->orderBy('display_name');
        if ($request->filled('search')) {
            $search = trim((string) $request->string('search'));
            $query->where(function ($builder) use ($search): void {
                $builder->where('display_name', 'like', "%{$search}%")
                    ->orWhere('name', 'like', "%{$search}%");
            });
        }

        return view('admin.roles.index', ['roles' => $query->get()]);
    }

    public function create(Request $request): View
    {
        return view('admin.roles.create', [
            'permissionGroups' => $this->delegation
                ->permissionOptions($request->user())
                ->groupBy('group'),
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'name' => [
                'required',
                'string',
                'max:100',
                'regex:/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/',
                Rule::unique('roles', 'name'),
            ],
            'display_name' => ['required', 'string', 'max:150'],
            'description' => ['nullable', 'string', 'max:1000'],
            'permission_ids' => ['nullable', 'array'],
            'permission_ids.*' => ['integer', 'exists:permissions,id'],
        ]);

        $permissionIds = $data['permission_ids'] ?? [];
        $this->delegation->assertPermissionsDelegable($request->user(), $permissionIds);

        $role = DB::transaction(function () use ($data, $permissionIds): Role {
            $role = Role::query()->create([
                'name' => $data['name'],
                'guard_name' => PermissionCatalog::GUARD,
                'display_name' => $data['display_name'],
                'description' => $data['description'] ?? null,
                'is_system' => false,
            ]);

            $this->authorization->syncRolePermissionIds($role, $permissionIds);

            return $role->fresh('permissions');
        });

        AuditService::record(
            'authorization.role.created',
            $role,
            [],
            [
                'name' => $role->name,
                'display_name' => $role->display_name,
                'permissions' => $role->permissions->pluck('name')->sort()->values()->all(),
            ],
        );

        return redirect()->route('admin.roles.index')->with('success', 'Custom role created.');
    }

    public function edit(Request $request, Role $role): View
    {
        $this->delegation->assertCustomRoleMutable($role);

        return view('admin.roles.edit', [
            'role' => $role->load('permissions'),
            'permissionGroups' => $this->delegation
                ->permissionOptions($request->user())
                ->groupBy('group'),
        ]);
    }

    public function update(Request $request, Role $role): RedirectResponse
    {
        $this->delegation->assertCustomRoleMutable($role);

        $data = $request->validate([
            'display_name' => ['required', 'string', 'max:150'],
            'description' => ['nullable', 'string', 'max:1000'],
            'permission_ids' => ['nullable', 'array'],
            'permission_ids.*' => ['integer', 'exists:permissions,id'],
        ]);

        $permissionIds = $data['permission_ids'] ?? [];
        $this->delegation->assertPermissionsDelegable($request->user(), $permissionIds);

        $before = [
            'display_name' => $role->display_name,
            'description' => $role->description,
            'permissions' => $role->permissions()->pluck('permissions.name')->sort()->values()->all(),
        ];

        DB::transaction(function () use ($role, $data, $permissionIds): void {
            $role->update([
                'display_name' => $data['display_name'],
                'description' => $data['description'] ?? null,
            ]);
            $this->authorization->syncRolePermissionIds($role, $permissionIds);
        });

        $fresh = $role->fresh('permissions');
        $after = [
            'display_name' => $fresh->display_name,
            'description' => $fresh->description,
            'permissions' => $fresh->permissions->pluck('name')->sort()->values()->all(),
        ];

        AuditService::record('authorization.role.updated', $fresh, $before, $after);

        return redirect()->route('admin.roles.index')->with('success', 'Custom role updated.');
    }

    public function destroy(Role $role): RedirectResponse
    {
        $this->delegation->assertCustomRoleMutable($role);
        $role->loadCount('users')->load('permissions');

        abort_if(
            $role->users_count > 0,
            422,
            'A role assigned to staff cannot be deleted. Remove its user assignments first.',
        );

        $before = [
            'name' => $role->name,
            'display_name' => $role->display_name,
            'permissions' => $role->permissions->pluck('name')->sort()->values()->all(),
        ];

        AuditService::record('authorization.role.deleted', $role, $before, []);
        $role->delete();

        return redirect()->route('admin.roles.index')->with('success', 'Custom role deleted.');
    }
}
