<?php

namespace App\Services;

use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Support\Collection;

class TenantRoleService
{
    /**
     * System role name slugs. Used when creating custom roles; these names are reserved.
     */
    public const RESERVED_ROLE_NAMES = ['admin', 'hr', 'manager', 'finance', 'employee'];

    /**
     * Default permission names for the tenant "employee" role.
     */
    public static function employeePermissionNames(): array
    {
        return [
            'dashboard_view',
            'attendances_view',
            'leaves_view', 'leaves_create',
            'documents_view', 'documents_create',
            'expenses_create_own', 'finance_self_view',
            'recruitment_refer', 'recruitment_referrals_view',
            'announcements_view',
            'complaints_view_own', 'complaints_create_own',
            'warnings_view_own',
            'promotions_view_own', 'appreciations_view_own', 'terminations_view_own',
        ];
    }

    /**
     * Line manager: team oversight and core HR-adjacent views, no company settings or full recruitment write.
     */
    public static function managerPermissionNames(): array
    {
        return [
            'dashboard_view',
            'users_view',
            'attendances_view', 'attendances_create', 'attendances_edit', 'attendances_approve_regularization',
            'biometric_devices_view',
            'leaves_view', 'leaves_create', 'leaves_approve_reject', 'leave_types_create',
            'documents_view', 'documents_create',
            'hr_operations_view', 'announcements_view',
            'view_reports',
            'expenses_create_own', 'finance_self_view', 'finance_team_view', 'expenses_approve',
            'recruitment_refer', 'recruitment_referrals_view',
        ];
    }

    /**
     * Finance: treasury (accounts, deposits) + operational expenses/advances; no people or payroll run.
     */
    public static function financePermissionNames(): array
    {
        return [
            'dashboard_view',
            'finance_view', 'finance_treasury_view', 'finance_operational',
            'finance_create', 'finance_edit', 'finance_delete',
            'expenses_approve', 'expenses_create_own', 'finance_self_view',
            'payrolls_view',
            'view_reports',
        ];
    }

    /**
     * features; no company-wide `manage_settings` or finance; includes role self-service for the tenant.
     */
    public static function hrPermissionNames(): array
    {
        return [
            'dashboard_view',
            'users_view', 'users_create', 'users_edit', 'users_delete',
            'attendances_view', 'attendances_create', 'attendances_edit', 'attendances_delete', 'attendances_approve_regularization',
            'biometric_devices_view', 'biometric_devices_manage',
            'leaves_view', 'leaves_create', 'leaves_edit', 'leaves_delete', 'leaves_approve_reject', 'leave_types_create',
            'payrolls_view', 'payrolls_create', 'payrolls_edit', 'payrolls_delete',
            'departments_view', 'departments_create', 'departments_edit', 'departments_delete',
            'roles_view', 'roles_create', 'roles_edit', 'roles_delete',
            'view_reports',
            'assets_view', 'assets_create', 'assets_edit', 'assets_delete',
            'documents_view', 'documents_create', 'documents_edit', 'documents_delete',
            'hr_operations_view', 'hr_operations_create', 'hr_operations_edit', 'hr_operations_delete',
            'announcements_view', 'announcements_create', 'announcements_edit', 'announcements_delete',
            'recruitment_view', 'recruitment_refer', 'recruitment_referrals_view', 'recruitment_create', 'recruitment_edit', 'recruitment_delete',
            'performance_view', 'performance_create', 'performance_edit', 'performance_delete',
            'finance_operational', 'finance_create', 'finance_edit',
            'expenses_approve', 'expenses_create_own', 'finance_self_view',
        ];
    }

    /**
     * @param  array<string, int>  $permissionNameToId
     * @return array<int>
     */
    public static function mapPermissionIds(array $names, array $permissionNameToId): array
    {
        return array_values(array_filter(
            array_map(fn (string $name) => $permissionNameToId[$name] ?? null, $names)
        ));
    }

    /**
     * @param  array<string, int>  $permissions  name => id
     * @return array<int>
     */
    public static function mapEmployeePermissionIds(array $permissions): array
    {
        return self::mapPermissionIds(self::employeePermissionNames(), $permissions);
    }

    public static function ensurePermissionsSeeded(): void
    {
        (new RolePermissionSeeder())->run();
    }

    /**
     * (Re)sync the four system roles for a company. Use from artisan reset and tenant bootstrap.
     *
     * @return array{admin: Role, hr: Role, manager: Role, finance: Role, employee: Role}
     */
    public static function syncAllDefaultRolesForCompany(int $companyId, bool $dryRun = false): array
    {
        self::ensurePermissionsSeeded();
        $permMap = Permission::query()->pluck('id', 'name')->all();
        if (count($permMap) === 0) {
            throw new \RuntimeException('No permissions in database. Run RolePermissionSeeder.');
        }
        $allIds = array_values($permMap);

        $admin = Role::withoutGlobalScopes()->firstOrCreate(
            ['company_id' => $companyId, 'name' => 'admin'],
            ['display_name' => 'Admin']
        );
        $hr = Role::withoutGlobalScopes()->firstOrCreate(
            ['company_id' => $companyId, 'name' => 'hr'],
            ['display_name' => 'HR']
        );
        $manager = Role::withoutGlobalScopes()->firstOrCreate(
            ['company_id' => $companyId, 'name' => 'manager'],
            ['display_name' => 'Manager']
        );
        $finance = Role::withoutGlobalScopes()->firstOrCreate(
            ['company_id' => $companyId, 'name' => 'finance'],
            ['display_name' => 'Finance']
        );
        $employee = Role::withoutGlobalScopes()->firstOrCreate(
            ['company_id' => $companyId, 'name' => 'employee'],
            ['display_name' => 'Employee']
        );

        if (! $dryRun) {
            $admin->permissions()->sync($allIds);
            $hr->permissions()->sync(self::mapPermissionIds(self::hrPermissionNames(), $permMap));
            $manager->permissions()->sync(self::mapPermissionIds(self::managerPermissionNames(), $permMap));
            $finance->permissions()->sync(self::mapPermissionIds(self::financePermissionNames(), $permMap));
            $employee->permissions()->sync(self::mapEmployeePermissionIds($permMap));
        }

        return [
            'admin' => $admin,
            'hr' => $hr,
            'manager' => $manager,
            'finance' => $finance,
            'employee' => $employee,
        ];
    }

    /**
     * Ensure the company has an "employee" role with standard staff permissions.
     * Does not re-sync other system roles (avoids work on every new hire).
     */
    public static function ensureEmployeeRoleForCompany(int $companyId): Role
    {
        self::ensurePermissionsSeeded();
        $permMap = Permission::query()->pluck('id', 'name')->all();
        $role = Role::withoutGlobalScopes()->firstOrCreate(
            ['company_id' => $companyId, 'name' => 'employee'],
            ['display_name' => 'Employee']
        );
        $role->permissions()->sync(self::mapEmployeePermissionIds($permMap));

        return $role;
    }

    public static function isReservedRoleName(string $name): bool
    {
        return in_array(strtolower(trim($name)), self::RESERVED_ROLE_NAMES, true);
    }

    /**
     * Assign the tenant "employee" role when the user has no role (e.g. legacy accounts).
     * Skips super admins, users who already have a role, and users with a system line-management / HR role.
     */
    public static function assignEmployeeRoleIfMissing(User $user): void
    {
        if ($user->isSuperAdmin() || $user->isTenantManager()) {
            return;
        }
        if (! $user->company_id || $user->role_id !== null) {
            return;
        }
        $role = self::ensureEmployeeRoleForCompany((int) $user->company_id);
        $user->forceFill(['role_id' => $role->id])->save();
    }

    /**
     * Group permissions for role create/edit UI (stable order, human-friendly sections).
     *
     * @param  Collection<int, Permission>  $permissions
     * @return list<array{key: string, label: string, icon: string, permissions: Collection<int, Permission>}>
     */
    public static function groupPermissionsForUi(Collection $permissions): array
    {
        $groupKey = static function (string $name): string {
            return match (true) {
                str_starts_with($name, 'dashboard') => 'dashboard',
                str_starts_with($name, 'users') => 'people',
                str_starts_with($name, 'departments') => 'org',
                str_starts_with($name, 'attendances') => 'attendance',
                str_starts_with($name, 'leaves') => 'leaves',
                str_starts_with($name, 'leave_types') => 'leaves',
                str_starts_with($name, 'payrolls') => 'payroll',
                str_starts_with($name, 'roles') => 'roles',
                $name === 'view_reports' => 'reports',
                $name === 'manage_settings' => 'settings',
                str_starts_with($name, 'assets') => 'assets',
                str_starts_with($name, 'finance') => 'finance',
                str_starts_with($name, 'documents') => 'documents',
                str_starts_with($name, 'hr_operations') => 'hr_ops',
                str_starts_with($name, 'announcements') => 'announcements',
                str_starts_with($name, 'recruitment') => 'recruitment',
                str_starts_with($name, 'performance') => 'performance',
                default => 'other',
            };
        };

        $meta = [
            'dashboard' => ['label' => 'Dashboard', 'icon' => 'bi-house-door'],
            'people' => ['label' => 'People & employees', 'icon' => 'bi-people'],
            'org' => ['label' => 'Departments & org', 'icon' => 'bi-diagram-3'],
            'attendance' => ['label' => 'Attendance & shifts', 'icon' => 'bi-clock-history'],
            'leaves' => ['label' => 'Leave management', 'icon' => 'bi-calendar2-week'],
            'payroll' => ['label' => 'Payroll & salary', 'icon' => 'bi-currency-dollar'],
            'assets' => ['label' => 'Assets', 'icon' => 'bi-box-seam'],
            'finance' => ['label' => 'Finance & accounts', 'icon' => 'bi-bank'],
            'documents' => ['label' => 'Documents & letters', 'icon' => 'bi-file-earmark-text'],
            'hr_ops' => ['label' => 'HR operations', 'icon' => 'bi-briefcase'],
            'announcements' => ['label' => 'Announcements', 'icon' => 'bi-megaphone'],
            'recruitment' => ['label' => 'Recruitment & hiring', 'icon' => 'bi-person-workspace'],
            'performance' => ['label' => 'Performance & goals', 'icon' => 'bi-graph-up-arrow'],
            'roles' => ['label' => 'Roles & access', 'icon' => 'bi-shield-lock'],
            'reports' => ['label' => 'Reports & analytics', 'icon' => 'bi-bar-chart-line'],
            'settings' => ['label' => 'Company settings', 'icon' => 'bi-gear'],
            'other' => ['label' => 'Other', 'icon' => 'bi-grid'],
        ];

        $buckets = [];
        foreach (array_keys($meta) as $bucketKey) {
            $buckets[$bucketKey] = new Collection;
        }
        foreach ($permissions as $p) {
            $k = $groupKey($p->name);
            if (! array_key_exists($k, $buckets)) {
                $k = 'other';
            }
            $buckets[$k] = $buckets[$k]->push($p);
        }

        $out = [];
        foreach (array_keys($meta) as $gkey) {
            if ($buckets[$gkey]->isEmpty()) {
                continue;
            }
            $out[] = [
                'key' => $gkey,
                'label' => $meta[$gkey]['label'],
                'icon' => $meta[$gkey]['icon'],
                'permissions' => $buckets[$gkey]->sortBy('name')->values(),
            ];
        }

        return $out;
    }

    public static function humanizePermissionName(string $name): string
    {
        return (string) str($name)->replace('_', ' ')->headline();
    }

    /**
     * @param  list<array{key: string, label: string, icon: string, permissions: Collection<int, Permission>}>  $grouped
     * @return list<array{key: string, label: string, icon: string, items: list<array{id: int, name: string, d: string}>}>
     */
    public static function mapPermissionGroupsForView(array $grouped): array
    {
        $out = [];
        foreach ($grouped as $g) {
            $out[] = [
                'key' => $g['key'],
                'label' => $g['label'],
                'icon' => $g['icon'],
                'items' => $g['permissions']->map(function (Permission $p) {
                    return [
                        'id' => (int) $p->id,
                        'name' => $p->name,
                        'd' => $p->display_name ?? self::humanizePermissionName($p->name),
                    ];
                })->values()->all(),
            ];
        }

        return $out;
    }
}
