<?php
declare(strict_types=1);

namespace App\Modules\Users\Models;

use App\Core\Model;
use App\Core\App;

class Employee extends Model
{
    protected static string $table = 'employees';
    protected static string $primaryKey = 'id';
    protected static bool $timestamps = true;
    protected static bool $softDelete = true;
    protected static bool $dispatchEvents = true;

    protected static array $fillable = [
        'username', 'password_hash', 'full_name_ar', 'full_name_en',
        'email', 'phone', 'branch_id', 'is_active', 'force_password_change',
        'password_changed_at', 'last_login_at', 'last_login_ip',
        'failed_login_count', 'locked_until',
    ];

    private ?array $cachedPermissions = null;
    private ?array $cachedDenials = null;
    private ?array $cachedRoles = null;

    public static function findByUsername(string $username): ?static
    {
        $row = static::query()->withArchived()->whereRaw("`employees`.`username` = ?", [$username])->first();
        if ($row === null) {
            return null;
        }
        $instance = new static($row);
        $instance->exists = true;
        return $instance;
    }

    public function getAllPermissions(): array
    {
        if ($this->cachedPermissions !== null) {
            return $this->cachedPermissions;
        }

        $db = App::getInstance()->db();
        $sql = "
            SELECT DISTINCT rp.permission_key
            FROM employee_roles er
            JOIN role_permissions rp ON rp.role_id = er.role_id
            JOIN roles r ON r.id = er.role_id AND r.is_active = 1
            WHERE er.employee_id = ?
              AND er.is_active = 1
              AND (er.expires_at IS NULL OR er.expires_at > NOW())
        ";
        $rows = $db->select($sql, [$this->id]);
        $permissions = array_column($rows, 'permission_key');

        $roleIds = $this->getRoleIds();
        $parentPerms = $this->getParentRolePermissions($roleIds);
        $permissions = array_unique(array_merge($permissions, $parentPerms));

        try {
            $directRows = $db->select(
                "SELECT permission_key, is_denied FROM employee_permissions WHERE employee_id = ?",
                [$this->id]
            );

            $directGrants = [];
            $directDenials = [];
            foreach ($directRows as $row) {
                if ((int) $row['is_denied'] === 1) {
                    $directDenials[] = $row['permission_key'];
                } else {
                    $directGrants[] = $row['permission_key'];
                }
            }

            $permissions = array_unique(array_merge($permissions, $directGrants));
            $permissions = array_values(array_diff($permissions, $directDenials));
            $this->cachedDenials = $directDenials;
        } catch (\Throwable $e) {
            $this->cachedDenials = [];
        }

        $this->cachedPermissions = $permissions;
        return $this->cachedPermissions;
    }

    private function getRoleIds(): array
    {
        $db = App::getInstance()->db();
        $rows = $db->select(
            "SELECT role_id FROM employee_roles WHERE employee_id = ? AND is_active = 1 AND (expires_at IS NULL OR expires_at > NOW())",
            [$this->id]
        );
        return array_column($rows, 'role_id');
    }

    private function getParentRolePermissions(array $roleIds): array
    {
        if (empty($roleIds)) {
            return [];
        }

        $db = App::getInstance()->db();
        $permissions = [];
        $visited = [];

        foreach ($roleIds as $roleId) {
            $this->collectParentPermissions((int) $roleId, $permissions, $visited, $db);
        }

        return $permissions;
    }

    private function collectParentPermissions(int $roleId, array &$permissions, array &$visited, $db): void
    {
        if (in_array($roleId, $visited)) {
            return;
        }
        $visited[] = $roleId;

        $role = $db->selectOne("SELECT parent_role_id FROM roles WHERE id = ? AND is_active = 1", [$roleId]);
        if (!$role || !$role['parent_role_id']) {
            return;
        }

        $parentId = (int) $role['parent_role_id'];
        $rows = $db->select("SELECT permission_key FROM role_permissions WHERE role_id = ?", [$parentId]);
        foreach ($rows as $row) {
            $permissions[] = $row['permission_key'];
        }

        $this->collectParentPermissions($parentId, $permissions, $visited, $db);
    }

    public function hasPermission(string $key): bool
    {
        $permissions = $this->getAllPermissions();
        if (in_array('*', $permissions, true)) {
            return true;
        }
        if (in_array($key, $this->cachedDenials ?? [], true)) {
            return false;
        }
        return in_array($key, $permissions, true);
    }

    public function hasAnyPermission(array $keys): bool
    {
        foreach ($keys as $key) {
            if ($this->hasPermission($key)) {
                return true;
            }
        }
        return false;
    }

    public function hasRole(string $code): bool
    {
        $roles = $this->getRoleCodes();
        return in_array($code, $roles, true);
    }

    public function getRoleCodes(): array
    {
        if ($this->cachedRoles !== null) {
            return $this->cachedRoles;
        }

        $db = App::getInstance()->db();
        $sql = "
            SELECT r.role_code FROM employee_roles er
            JOIN roles r ON r.id = er.role_id AND r.is_active = 1
            WHERE er.employee_id = ? AND er.is_active = 1
            AND (er.expires_at IS NULL OR er.expires_at > NOW())
        ";
        $rows = $db->select($sql, [$this->id]);
        $this->cachedRoles = array_column($rows, 'role_code');
        return $this->cachedRoles;
    }

    public function getBranchId(): ?int
    {
        return $this->branch_id ? (int) $this->branch_id : null;
    }

    public function isLocked(): bool
    {
        if (!$this->locked_until) {
            return false;
        }
        return strtotime($this->locked_until) > time();
    }

    public function incrementFailedLogins(): void
    {
        $db = App::getInstance()->db();
        $count = (int) $this->failed_login_count + 1;
        $data = ['failed_login_count' => $count, 'updated_at' => date('Y-m-d H:i:s')];

        $maxAttempts = (int) config('auth.max_failed_attempts', 5);
        if ($count >= $maxAttempts) {
            $lockoutMinutes = (int) config('auth.lockout_minutes', 15);
            $data['locked_until'] = date('Y-m-d H:i:s', time() + ($lockoutMinutes * 60));
        }

        $db->update('employees', $data, '`id` = ?', [$this->id]);
    }

    public function resetFailedLogins(): void
    {
        $db = App::getInstance()->db();
        $db->update('employees', [
            'failed_login_count' => 0,
            'locked_until'       => null,
            'updated_at'         => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$this->id]);
    }

    public function recordLogin(string $ip): void
    {
        $db = App::getInstance()->db();
        $db->update('employees', [
            'last_login_at'     => date('Y-m-d H:i:s'),
            'last_login_ip'     => $ip,
            'failed_login_count' => 0,
            'locked_until'      => null,
            'updated_at'        => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$this->id]);
    }

    public function getRolesWithNames(): array
    {
        $db = App::getInstance()->db();
        return $db->select("
            SELECT r.id, r.role_code, r.name_ar, r.name_en, er.assigned_at, er.expires_at
            FROM employee_roles er
            JOIN roles r ON r.id = er.role_id
            WHERE er.employee_id = ? AND er.is_active = 1
            ORDER BY r.name_ar
        ", [$this->id]);
    }

    public function getBranchName(): string
    {
        if (!$this->branch_id) {
            return 'جميع الفروع';
        }
        $db = App::getInstance()->db();
        $branch = $db->selectOne("SELECT name_ar FROM branches WHERE id = ?", [$this->branch_id]);
        return $branch['name_ar'] ?? '—';
    }

    public function getDirectPermissions(): array
    {
        $db = App::getInstance()->db();
        try {
            return $db->select(
                "SELECT permission_key, is_denied, granted_at, granted_by, notes
                 FROM employee_permissions WHERE employee_id = ? ORDER BY permission_key",
                [$this->id]
            );
        } catch (\Throwable $e) {
            return [];
        }
    }

    public function getEffectivePermissions(): array
    {
        $db = App::getInstance()->db();

        $rolePerms = $db->select("
            SELECT rp.permission_key, r.name_ar AS source
            FROM employee_roles er
            JOIN role_permissions rp ON rp.role_id = er.role_id
            JOIN roles r ON r.id = er.role_id AND r.is_active = 1
            WHERE er.employee_id = ? AND er.is_active = 1
              AND (er.expires_at IS NULL OR er.expires_at > NOW())
        ", [$this->id]);

        $result = [];
        foreach ($rolePerms as $rp) {
            $key = $rp['permission_key'];
            if (!isset($result[$key])) {
                $result[$key] = ['key' => $key, 'sources' => [], 'direct' => null];
            }
            $result[$key]['sources'][] = $rp['source'];
        }

        try {
            $directPerms = $db->select(
                "SELECT permission_key, is_denied, notes FROM employee_permissions WHERE employee_id = ?",
                [$this->id]
            );
            foreach ($directPerms as $dp) {
                $key = $dp['permission_key'];
                if (!isset($result[$key])) {
                    $result[$key] = ['key' => $key, 'sources' => [], 'direct' => null];
                }
                $result[$key]['direct'] = (int) $dp['is_denied'] === 1 ? 'deny' : 'grant';
            }
        } catch (\Throwable $e) {
        }

        ksort($result);
        return $result;
    }

    public function syncDirectPermissions(array $grants, array $denials, int $grantedBy): void
    {
        $db = App::getInstance()->db();
        $db->delete('employee_permissions', '`employee_id` = ?', [$this->id]);
        $now = date('Y-m-d H:i:s');

        foreach ($grants as $key) {
            $key = trim($key);
            if ($key === '') continue;
            $db->insert('employee_permissions', [
                'employee_id'    => (int) $this->id,
                'permission_key' => $key,
                'is_denied'      => 0,
                'granted_at'     => $now,
                'granted_by'     => $grantedBy,
            ]);
        }

        foreach ($denials as $key) {
            $key = trim($key);
            if ($key === '') continue;
            $db->insert('employee_permissions', [
                'employee_id'    => (int) $this->id,
                'permission_key' => $key,
                'is_denied'      => 1,
                'granted_at'     => $now,
                'granted_by'     => $grantedBy,
            ]);
        }

        $this->cachedPermissions = null;
        $this->cachedDenials = null;
    }
}