<?php

namespace App\Traits;

use App\Models\Role;
use App\Models\Permission;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;

trait HasRolesAndPermissions
{
    /**
     * Get user's roles
     */
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class, 'user_roles')
                    ->withPivot(['branch_code', 'is_active', 'assigned_at', 'expires_at'])
                    ->withTimestamps();
    }

    /**
     * Get user's direct permissions
     */
    public function permissions(): BelongsToMany
    {
        return $this->belongsToMany(Permission::class, 'user_permissions')
                    ->withPivot(['granted', 'branch_code', 'assigned_at', 'expires_at'])
                    ->withTimestamps();
    }

    /**
     * Check if user has a specific role
     */
    public function hasRole(string|array $roles, ?string $branchCode = null): bool
    {
        if (is_array($roles)) {
            return collect($roles)->some(fn($role) => $this->hasRole($role, $branchCode));
        }

        $query = $this->roles()->where('roles.name', $roles);
        
        if ($branchCode) {
            $query->where('user_roles.branch_code', $branchCode);
        }

        return $query->where('user_roles.is_active', true)
                    ->where(function ($q) {
                        $q->whereNull('user_roles.expires_at')
                          ->orWhere('user_roles.expires_at', '>', now());
                    })
                    ->exists();
    }

    /**
     * Check if user has any of the given roles
     */
    public function hasAnyRole(array $roles, ?string $branchCode = null): bool
    {
        return collect($roles)->some(fn($role) => $this->hasRole($role, $branchCode));
    }

    /**
     * Check if user has all of the given roles
     */
    public function hasAllRoles(array $roles, ?string $branchCode = null): bool
    {
        return collect($roles)->every(fn($role) => $this->hasRole($role, $branchCode));
    }

    /**
     * Check if user has a specific permission
     */
    public function hasPermission(string $permission, ?string $branchCode = null): bool
    {
        // Check direct permissions first
        $directPermission = $this->permissions()
            ->where('permissions.name', $permission)
            ->when($branchCode, fn($q) => $q->where('user_permissions.branch_code', $branchCode))
            ->where('user_permissions.granted', true)
            ->where(function ($q) {
                $q->whereNull('user_permissions.expires_at')
                  ->orWhere('user_permissions.expires_at', '>', now());
            })
            ->exists();

        if ($directPermission) {
            return true;
        }

        // Check permissions through roles
        $rolePermissions = $this->roles()
            ->when($branchCode, fn($q) => $q->where('user_roles.branch_code', $branchCode))
            ->where('user_roles.is_active', true)
            ->where(function ($q) {
                $q->whereNull('user_roles.expires_at')
                  ->orWhere('user_roles.expires_at', '>', now());
            })
            ->whereHas('permissions', function ($q) use ($permission) {
                $q->where('permissions.name', $permission)->where('permissions.is_active', true);
            })
            ->exists();

        return $rolePermissions;
    }

    /**
     * Check if user has any of the given permissions
     */
    public function hasAnyPermission(array $permissions, ?string $branchCode = null): bool
    {
        return collect($permissions)->some(fn($permission) => $this->hasPermission($permission, $branchCode));
    }

    /**
     * Check if user has all of the given permissions
     */
    public function hasAllPermissions(array $permissions, ?string $branchCode = null): bool
    {
        return collect($permissions)->every(fn($permission) => $this->hasPermission($permission, $branchCode));
    }

    /**
     * Assign a role to user
     */
    public function assignRole(string|Role $role, ?string $branchCode = null, ?\DateTime $expiresAt = null): self
    {
        if (is_string($role)) {
            $role = Role::where('name', $role)->first();
        }

        if ($role && !$this->hasRole($role->name, $branchCode)) {
            $this->roles()->attach($role->id, [
                'branch_code' => $branchCode,
                'is_active' => true,
                'assigned_at' => now(),
                'expires_at' => $expiresAt,
            ]);
        }

        return $this;
    }

    /**
     * Remove a role from user
     */
    public function removeRole(string|Role $role, ?string $branchCode = null): self
    {
        if (is_string($role)) {
            $role = Role::where('name', $role)->first();
        }

        if ($role) {
            $query = $this->roles()->where('role_id', $role->id);
            
            if ($branchCode) {
                $query->where('user_roles.branch_code', $branchCode);
            }
            
            $query->detach();
        }

        return $this;
    }

    /**
     * Give permission directly to user
     */
    public function givePermission(string|Permission $permission, ?string $branchCode = null, ?\DateTime $expiresAt = null): self
    {
        if (is_string($permission)) {
            $permission = Permission::where('name', $permission)->first();
        }

        if ($permission) {
            $this->permissions()->syncWithoutDetaching([
                $permission->id => [
                    'granted' => true,
                    'branch_code' => $branchCode,
                    'assigned_at' => now(),
                    'expires_at' => $expiresAt,
                ]
            ]);
        }

        return $this;
    }

    /**
     * Revoke permission from user
     */
    public function revokePermission(string|Permission $permission, ?string $branchCode = null): self
    {
        if (is_string($permission)) {
            $permission = Permission::where('name', $permission)->first();
        }

        if ($permission) {
            $query = $this->permissions()->where('permission_id', $permission->id);
            
            if ($branchCode) {
                $query->where('user_permissions.branch_code', $branchCode);
            }
            
            $query->detach();
        }

        return $this;
    }

    /**
     * Get all user permissions (from roles and direct)
     */
    public function getAllPermissions(?string $branchCode = null): Collection
    {
        // Get direct permissions
        $directPermissions = $this->permissions()
            ->when($branchCode, fn($q) => $q->where('user_permissions.branch_code', $branchCode))
            ->where('user_permissions.granted', true)
            ->where(function ($q) {
                $q->whereNull('user_permissions.expires_at')
                  ->orWhere('user_permissions.expires_at', '>', now());
            })
            ->get();

        // Get permissions through roles
        $rolePermissions = Permission::whereHas('roles.users', function ($q) use ($branchCode) {
            $q->where('user_id', $this->id)
              ->when($branchCode, fn($query) => $query->where('user_roles.branch_code', $branchCode))
              ->where('user_roles.is_active', true)
              ->where(function ($query) {
                  $query->whereNull('user_roles.expires_at')
                        ->orWhere('user_roles.expires_at', '>', now());
              });
        })->where('is_active', true)->get();

        return $directPermissions->merge($rolePermissions)->unique('id');
    }

    /**
     * Check if user can access a specific module
     */
    public function canAccessModule(string $module, ?string $branchCode = null): bool
    {
        return $this->getAllPermissions($branchCode)
                    ->where('module', $module)
                    ->isNotEmpty();
    }
}
