<?php

namespace Database\Seeders;

use App\Models\Role;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\PermissionRegistrar;

class PermissionSeeder extends Seeder
{
    /**
     * Module key for user-account permissions.
     */
    public const USER_MANAGEMENT_MODULE = 'users';

    /**
     * Module key for role management permissions.
     */
    public const ROLES_MODULE = 'roles';

    /**
     * Module key for staff management (designations, employees, attendance).
     */
    public const STAFF_MANAGEMENT_MODULE = 'staff';

    /**
     * Permission names withheld from the Admin role (Master Admin keeps all).
     *
     * @return list<string>
     */
    public static function adminWithheldPermissionNames(): array
    {
        return array_values(array_unique([
            ...self::userManagementPermissionNames(),
            ...self::rolesPermissionNames(),
        ]));
    }

    /**
     * Permission names grouped by module; extend as the app grows.
     *
     * @var array<string, list<string>>
     */
    public const PERMISSIONS_BY_MODULE = [
        'dashboard' => [
            'dashboard.view',
        ],
        'roles' => [
            'roles.view',
            'roles.create',
            'roles.update',
        ],
        'users' => [
            'users.view',
            'users.create',
            'users.update',
            'users.audit',
            'users.login-history',
        ],
        'designations' => [
            'designations.view',
            'designations.create',
            'designations.update',
            'designations.delete',
        ],
        'permanent-employees' => [
            'permanent-employees.view',
            'permanent-employees.create',
            'permanent-employees.update',
        ],
        'permanent-salaries' => [
            'permanent-salaries.view',
            'permanent-salaries.manage',
        ],
        'permanent-punishments' => [
            'permanent-punishments.view',
            'permanent-punishments.create',
            'permanent-punishments.update',
        ],
        'permanent-advances' => [
            'permanent-advances.view',
            'permanent-advances.create',
            'permanent-advances.update',
        ],
        'temporary-employees' => [
            'temporary-employees.view',
            'temporary-employees.create',
            'temporary-employees.update',
        ],
        'temporary-contracts' => [
            'temporary-contracts.view',
            'temporary-contracts.create',
            'temporary-contracts.update',
            'temporary-contracts.pay',
        ],
        'attendance' => [
            'attendance.view',
            'attendance.create',
        ],
        'attendance-devices' => [
            'attendance-devices.view',
            'attendance-devices.create',
            'attendance-devices.update',
        ],
        'attendance-enrollments' => [
            'attendance-enrollments.view',
            'attendance-enrollments.create',
            'attendance-enrollments.delete',
        ],
        'attendance-leaves' => [
            'attendance-leaves.view',
            'attendance-leaves.create',
            'attendance-leaves.update',
            'attendance-leaves.delete',
        ],
        'customers' => [
            'customers.view',
            'customers.create',
            'customers.update',
        ],
        'customer-payments' => [
            'customer-payments.view',
            'customer-payments.create',
        ],
        'customer-ledger' => [
            'customer-ledger.view',
        ],
        'customer-loans' => [
            'customer-loans.view',
            'customer-loans.create',
            'customer-loans.update',
            'customer-loans.repay',
        ],
        'purchase-units' => [
            'purchase-units.view',
            'purchase-units.create',
            'purchase-units.update',
        ],
        'purchase-products' => [
            'purchase-products.view',
            'purchase-products.create',
            'purchase-products.update',
        ],
        'factory-units' => [
            'factory-units.view',
            'factory-units.create',
            'factory-units.update',
        ],
        'factory-products' => [
            'factory-products.view',
            'factory-products.create',
            'factory-products.update',
        ],
        'production-formulas' => [
            'production-formulas.view',
            'production-formulas.create',
            'production-formulas.update',
        ],
        'purchases' => [
            'purchases.view',
            'purchases.create',
            'purchases.update',
        ],
        'purchase-suppliers' => [
            'purchase-suppliers.view',
            'purchase-suppliers.create',
            'purchase-suppliers.update',
        ],
        'supplier-payments' => [
            'supplier-payments.view',
            'supplier-payments.create',
        ],
        'customer-orders' => [
            'customer-orders.view',
            'customer-orders.create',
            'customer-orders.update',
        ],
        'sales' => [
            'sales.view',
            'sales.create',
            'sales.update',
            'sales.payment',
            'sales.return',
            'sales.exchange',
        ],
        'production-orders' => [
            'production-orders.view',
            'production-orders.complete',
        ],
        'dough-usage' => [
            'dough-usage.view',
        ],
        'expenses' => [
            'expenses.view',
            'expenses.create',
            'expenses.edit',
            'expenses.delete',
        ],
        'expense-categories' => [
            'expense-categories.manage',
        ],
        'expense-reports' => [
            'expense-reports.view',
        ],
        'cash-handovers' => [
            'cash-handovers.view',
            'cash-handovers.create',
            'cash-handovers.settle',
        ],
        'sarafs' => [
            'sarafs.view',
            'sarafs.create',
            'sarafs.update',
        ],
        'moraqebat-machines' => [
            'moraqebat-machines.view',
            'moraqebat-machines.create',
            'moraqebat-machines.update',
        ],
        'moraqebat-checklists' => [
            'moraqebat-checklists.view',
            'moraqebat-checklists.create',
        ],
    ];

    /**
     * Register Spatie permissions and sync default role assignments.
     */
    public function run(): void
    {
        $guard = 'web';

        foreach (self::allPermissionNames() as $name) {
            Permission::firstOrCreate(
                ['name' => $name, 'guard_name' => $guard],
            );
        }

        RoleSeeder::syncDefaultRolePermissions();
        self::syncPurchaseSuppliersToPurchaseRoles();
        self::syncSalesExchangeToReturnRoles();
        self::syncSalesPaymentToCreateRoles();
        self::syncSalesUpdateToCreateRoles();
        self::syncCustomerPaymentsToSalesPaymentRoles();
        self::syncCustomerLoansToCustomerPaymentRoles();
        self::syncCustomerLedgerToCustomerPaymentRoles();
        self::syncDoughUsageToProductionViewRoles();
        self::syncProductionFormulasToFactoryProductRoles();
        self::syncMoraqebatChecklistsToMachineRoles();
        self::purgeRetiredPermissions();

        app(PermissionRegistrar::class)->forgetCachedPermissions();
    }

    /**
     * Remove permissions that are no longer registered in {@see PERMISSIONS_BY_MODULE}.
     */
    public static function purgeRetiredPermissions(): void
    {
        $guard = 'web';
        $active = self::allPermissionNames();

        Permission::query()
            ->where('guard_name', $guard)
            ->whereNotIn('name', $active)
            ->each(function (Permission $permission): void {
                $permission->roles()->detach();
                $permission->users()->detach();
                $permission->delete();
            });
    }

    /**
     * Grant supplier permissions to roles that already have other purchase access.
     */
    public static function syncPurchaseSuppliersToPurchaseRoles(): void
    {
        $guard = 'web';

        $purchaseTriggerPermissions = array_values(array_unique(array_merge(
            self::PERMISSIONS_BY_MODULE['purchases'],
            self::PERMISSIONS_BY_MODULE['purchase-units'],
            self::PERMISSIONS_BY_MODULE['purchase-products'],
            self::PERMISSIONS_BY_MODULE['purchase-suppliers'],
        )));

        $supplierPaymentPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', self::PERMISSIONS_BY_MODULE['supplier-payments'])
            ->get();

        $supplierPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', self::PERMISSIONS_BY_MODULE['purchase-suppliers'])
            ->get();

        if ($supplierPermissions->isEmpty() && $supplierPaymentPermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->where('name', '!=', 'Master Admin')
            ->each(function (Role $role) use ($purchaseTriggerPermissions, $supplierPermissions, $supplierPaymentPermissions): void {
                $hasPurchaseAccess = collect($purchaseTriggerPermissions)
                    ->contains(fn (string $permission): bool => $role->hasPermissionTo($permission));

                if ($hasPurchaseAccess) {
                    if ($supplierPermissions->isNotEmpty()) {
                        $role->givePermissionTo($supplierPermissions);
                    }

                    if ($supplierPaymentPermissions->isNotEmpty()) {
                        $role->givePermissionTo($supplierPaymentPermissions);
                    }
                }
            });
    }

    /**
     * Grant exchange permission to roles that can already process returns.
     */
    public static function syncSalesExchangeToReturnRoles(): void
    {
        $guard = 'web';

        $exchangePermission = Permission::query()
            ->where('guard_name', $guard)
            ->where('name', 'sales.exchange')
            ->first();

        if ($exchangePermission === null) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($exchangePermission): void {
                if ($role->hasPermissionTo('sales.return') && ! $role->hasPermissionTo('sales.exchange')) {
                    $role->givePermissionTo($exchangePermission);
                }
            });
    }

    /**
     * Grant sale payment permission to roles that can already create sales.
     */
    public static function syncSalesPaymentToCreateRoles(): void
    {
        $guard = 'web';

        $paymentPermission = Permission::query()
            ->where('guard_name', $guard)
            ->where('name', 'sales.payment')
            ->first();

        if ($paymentPermission === null) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($paymentPermission): void {
                if ($role->hasPermissionTo('sales.create') && ! $role->hasPermissionTo('sales.payment')) {
                    $role->givePermissionTo($paymentPermission);
                }
            });
    }

    /**
     * Grant customer payment permissions to roles that can record sale payments.
     */
    public static function syncCustomerPaymentsToSalesPaymentRoles(): void
    {
        $guard = 'web';

        $customerPaymentPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', self::PERMISSIONS_BY_MODULE['customer-payments'])
            ->get();

        if ($customerPaymentPermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($customerPaymentPermissions): void {
                if (! $role->hasPermissionTo('sales.payment')) {
                    return;
                }

                foreach ($customerPaymentPermissions as $permission) {
                    if (! $role->hasPermissionTo($permission)) {
                        $role->givePermissionTo($permission);
                    }
                }
            });
    }

    /**
     * Grant customer loan permissions to roles that can record customer payments.
     */
    public static function syncCustomerLoansToCustomerPaymentRoles(): void
    {
        $guard = 'web';

        $loanPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', self::PERMISSIONS_BY_MODULE['customer-loans'])
            ->get();

        if ($loanPermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($loanPermissions): void {
                if (! $role->hasPermissionTo('customer-payments.create')) {
                    return;
                }

                foreach ($loanPermissions as $permission) {
                    if (! $role->hasPermissionTo($permission)) {
                        $role->givePermissionTo($permission);
                    }
                }
            });
    }

    /**
     * Grant customer ledger access to roles that can view customer payments.
     */
    public static function syncCustomerLedgerToCustomerPaymentRoles(): void
    {
        $guard = 'web';

        $ledgerPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', self::PERMISSIONS_BY_MODULE['customer-ledger'])
            ->get();

        if ($ledgerPermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($ledgerPermissions): void {
                if (! $role->hasPermissionTo('customer-payments.view')) {
                    return;
                }

                foreach ($ledgerPermissions as $permission) {
                    if (! $role->hasPermissionTo($permission)) {
                        $role->givePermissionTo($permission);
                    }
                }
            });
    }

    /**
     * Grant dough usage report access to roles that can view production orders.
     */
    public static function syncDoughUsageToProductionViewRoles(): void
    {
        $guard = 'web';

        $doughPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', self::PERMISSIONS_BY_MODULE['dough-usage'])
            ->get();

        if ($doughPermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($doughPermissions): void {
                if (! $role->hasPermissionTo('production-orders.view')) {
                    return;
                }

                foreach ($doughPermissions as $permission) {
                    if (! $role->hasPermissionTo($permission)) {
                        $role->givePermissionTo($permission);
                    }
                }
            });
    }

    /**
     * Grant production formula permissions to roles that already manage factory products.
     */
    public static function syncProductionFormulasToFactoryProductRoles(): void
    {
        $guard = 'web';

        $viewPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->where('name', 'production-formulas.view')
            ->get();

        $managePermissions = Permission::query()
            ->where('guard_name', $guard)
            ->whereIn('name', ['production-formulas.create', 'production-formulas.update'])
            ->get();

        if ($viewPermissions->isEmpty() && $managePermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($viewPermissions, $managePermissions): void {
                if ($role->hasPermissionTo('factory-products.view')) {
                    $role->givePermissionTo($viewPermissions);
                }

                if ($role->hasPermissionTo('factory-products.update')) {
                    $role->givePermissionTo($managePermissions);
                }
            });
    }

    /**
     * Grant sales update to roles that can already create sales.
     */
    public static function syncSalesUpdateToCreateRoles(): void
    {
        $guard = 'web';

        $updatePermission = Permission::query()
            ->where('guard_name', $guard)
            ->where('name', 'sales.update')
            ->first();

        if ($updatePermission === null) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($updatePermission): void {
                if ($role->hasPermissionTo('sales.create') && ! $role->hasPermissionTo('sales.update')) {
                    $role->givePermissionTo($updatePermission);
                }
            });
    }

    /**
     * @return list<string>
     */
    public static function allPermissionNames(): array
    {
        return array_values(array_unique(array_merge(...array_values(self::PERMISSIONS_BY_MODULE))));
    }

    /**
     * @return list<string>
     */
    public static function userManagementPermissionNames(): array
    {
        return self::PERMISSIONS_BY_MODULE[self::USER_MANAGEMENT_MODULE];
    }

    /**
     * @return list<string>
     */
    public static function rolesPermissionNames(): array
    {
        return self::PERMISSIONS_BY_MODULE[self::ROLES_MODULE];
    }

    /**
     * @return list<string>
     */
    public static function customerManagementPermissionNames(): array
    {
        return array_values(array_unique(array_merge(
            self::PERMISSIONS_BY_MODULE['customers'],
            self::PERMISSIONS_BY_MODULE['customer-payments'],
            self::PERMISSIONS_BY_MODULE['customer-ledger'],
            self::PERMISSIONS_BY_MODULE['customer-loans'],
            self::PERMISSIONS_BY_MODULE['customer-orders'],
        )));
    }

    /**
     * @return list<string>
     */
    public static function purchaseManagementPermissionNames(): array
    {
        return array_values(array_unique(array_merge(
            self::PERMISSIONS_BY_MODULE['purchase-units'],
            self::PERMISSIONS_BY_MODULE['purchase-products'],
            self::PERMISSIONS_BY_MODULE['purchase-suppliers'],
            self::PERMISSIONS_BY_MODULE['supplier-payments'],
            self::PERMISSIONS_BY_MODULE['purchases'],
        )));
    }

    /**
     * @return list<string>
     */
    public static function factoryManagementPermissionNames(): array
    {
        return array_values(array_unique(array_merge(
            self::PERMISSIONS_BY_MODULE['factory-units'],
            self::PERMISSIONS_BY_MODULE['factory-products'],
            self::PERMISSIONS_BY_MODULE['production-formulas'],
            self::PERMISSIONS_BY_MODULE['production-orders'],
            self::PERMISSIONS_BY_MODULE['dough-usage'],
        )));
    }

    /**
     * @return list<string>
     */
    public static function customerOrderPermissionNames(): array
    {
        return self::PERMISSIONS_BY_MODULE['customer-orders'];
    }

    /**
     * @return list<string>
     */
    public static function salesManagementPermissionNames(): array
    {
        return self::PERMISSIONS_BY_MODULE['sales'];
    }

    /**
     * @return list<string>
     */
    public static function expenseManagementPermissionNames(): array
    {
        return array_values(array_unique(array_merge(
            self::PERMISSIONS_BY_MODULE['expenses'],
            self::PERMISSIONS_BY_MODULE['expense-categories'],
            self::PERMISSIONS_BY_MODULE['expense-reports'],
        )));
    }

    /**
     * @return list<string>
     */
    public static function sarafiManagementPermissionNames(): array
    {
        return self::PERMISSIONS_BY_MODULE['sarafs'];
    }

    /**
     * @return list<string>
     */
    public static function cashHandoverPermissionNames(): array
    {
        return self::PERMISSIONS_BY_MODULE['cash-handovers'];
    }

    /**
     * @return list<string>
     */
    public static function moraqebatManagementPermissionNames(): array
    {
        return array_values(array_unique(array_merge(
            self::PERMISSIONS_BY_MODULE['moraqebat-machines'],
            self::PERMISSIONS_BY_MODULE['moraqebat-checklists'],
        )));
    }

    public static function syncMoraqebatChecklistsToMachineRoles(): void
    {
        $guard = 'web';

        $viewPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->where('name', 'moraqebat-checklists.view')
            ->get();

        $createPermissions = Permission::query()
            ->where('guard_name', $guard)
            ->where('name', 'moraqebat-checklists.create')
            ->get();

        if ($viewPermissions->isEmpty() && $createPermissions->isEmpty()) {
            return;
        }

        Role::query()
            ->where('guard_name', $guard)
            ->each(function (Role $role) use ($viewPermissions, $createPermissions): void {
                if ($role->hasPermissionTo('moraqebat-machines.view')) {
                    $role->givePermissionTo($viewPermissions);
                }

                if ($role->hasPermissionTo('moraqebat-machines.create')) {
                    $role->givePermissionTo($createPermissions);
                }
            });
    }

    /**
     * Staff management and attendance submenu permissions.
     *
     * @return list<string>
     */
    public static function staffManagementPermissionNames(): array
    {
        $modules = [
            'designations',
            'permanent-employees',
            'permanent-salaries',
            'permanent-punishments',
            'permanent-advances',
            'temporary-employees',
            'temporary-contracts',
            'attendance',
            'attendance-devices',
            'attendance-enrollments',
            'attendance-leaves',
        ];

        return array_values(array_unique(array_merge(
            ...array_map(
                fn (string $module): array => self::PERMISSIONS_BY_MODULE[$module],
                $modules,
            ),
        )));
    }
}
