<?php

namespace App\Core;

use PDO;

require_once __DIR__ . '/Session.php';
require_once __DIR__ . '/TenantPolicy.php';

class Auth
{
    private PDO $db;
    private array $permissions = [];

    /** سبب آخر فشل دخول (للمساعدة في التوجيه) */
    public ?array $lastLoginFailure = null;

    public function __construct(PDO $db)
    {
        Session::start();

        $this->db = $db;

        try {
            $this->db->exec("ALTER TABLE users ADD COLUMN failed_attempts INT NOT NULL DEFAULT 0");
        } catch (\Throwable $e) {
        }
        try {
            $this->db->exec("ALTER TABLE users ADD COLUMN locked_until DATETIME NULL");
        } catch (\Throwable $e) {
        }
        
        // إنشاء جدول IPs المحظورة (كل مختبر له IPs خاصة به)
        try {
            $this->db->exec(
                "CREATE TABLE IF NOT EXISTS blocked_ips (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    tenant_id INT NOT NULL DEFAULT 1,
                    ip_address VARCHAR(45) NOT NULL,
                    blocked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    blocked_until DATETIME NULL,
                    reason VARCHAR(255) NULL,
                    attempts_count INT NOT NULL DEFAULT 0,
                    is_permanent TINYINT(1) NOT NULL DEFAULT 0,
                    created_by INT NULL,
                    UNIQUE KEY uk_tenant_ip (tenant_id, ip_address),
                    INDEX idx_tenant (tenant_id),
                    INDEX idx_blocked_until (blocked_until)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
            );
        } catch (\Throwable $e) {
        }
        // جدول رموز تذكرني
        try {
            $this->db->exec(
                "CREATE TABLE IF NOT EXISTS remember_tokens (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    token_hash VARCHAR(64) NOT NULL,
                    expires_at DATETIME NOT NULL,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    INDEX idx_token (token_hash),
                    INDEX idx_user (user_id),
                    INDEX idx_expires (expires_at)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
            );
        } catch (\Throwable $e) {
        }
        // إنشاء جدول القائمة البيضاء للـ IPs (لا تُحظر أبداً)
        try {
            $this->db->exec(
                "CREATE TABLE IF NOT EXISTS whitelisted_ips (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    tenant_id INT NOT NULL DEFAULT 1,
                    ip_address VARCHAR(45) NOT NULL,
                    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    created_by INT NULL,
                    UNIQUE KEY uk_tenant_ip (tenant_id, ip_address),
                    INDEX idx_tenant (tenant_id)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
            );
        } catch (\Throwable $e) {
        }
    }
    
    /**
     * التحقق من قوة كلمة المرور
     */
    public static function validatePasswordStrength(string $password, ?array $policy = null): array
    {
        if ($policy === null) {
            $config = require __DIR__ . '/../../config/config.php';
            $policy = $config['security']['password_policy'] ?? [];
        }
        
        if (!($policy['enabled'] ?? false)) {
            return ['valid' => true, 'errors' => []];
        }
        
        $errors = [];
        $minLength = (int)($policy['min_length'] ?? 8);
        
        if (strlen($password) < $minLength) {
            $errors[] = "كلمة المرور يجب أن تكون على الأقل {$minLength} أحرف";
        }
        
        if (($policy['require_uppercase'] ?? false) && !preg_match('/[A-Z]/', $password)) {
            $errors[] = "كلمة المرور يجب أن تحتوي على حرف كبير واحد على الأقل";
        }
        
        if (($policy['require_lowercase'] ?? false) && !preg_match('/[a-z]/', $password)) {
            $errors[] = "كلمة المرور يجب أن تحتوي على حرف صغير واحد على الأقل";
        }
        
        if (($policy['require_numbers'] ?? false) && !preg_match('/[0-9]/', $password)) {
            $errors[] = "كلمة المرور يجب أن تحتوي على رقم واحد على الأقل";
        }
        
        if (($policy['require_special'] ?? false) && !preg_match('/[^A-Za-z0-9]/', $password)) {
            $errors[] = "كلمة المرور يجب أن تحتوي على رمز خاص واحد على الأقل";
        }
        
        return [
            'valid' => empty($errors),
            'errors' => $errors
        ];
    }
    
    /**
     * التحقق من أن IP في القائمة البيضاء (لا يُحظر أبداً)
     */
    public function isIpWhitelisted(string $ip, ?int $tenantId = null): bool
    {
        $tenantId = $tenantId ?? (int)($_SESSION['tenant_id'] ?? 1);
        try {
            $stmt = $this->db->prepare(
                "SELECT id FROM whitelisted_ips WHERE ip_address = :ip AND tenant_id = :tid LIMIT 1"
            );
            $stmt->execute(['ip' => $ip, 'tid' => $tenantId]);
            return $stmt->fetch() !== false;
        } catch (\Throwable $e) {
            return false;
        }
    }
    
    /**
     * التحقق من حظر IP (حسب المختبر — القائمة البيضاء لا تُحظر)
     */
    public function isIpBlocked(string $ip, ?int $tenantId = null): bool
    {
        if ($this->isIpWhitelisted($ip, $tenantId)) {
            return false;
        }
        $tenantId = $tenantId ?? (int)($_SESSION['tenant_id'] ?? 1);
        try {
            $stmt = $this->db->prepare(
                "SELECT id, blocked_until, is_permanent 
                 FROM blocked_ips 
                 WHERE ip_address = :ip AND tenant_id = :tid
                 AND (is_permanent = 1 OR blocked_until IS NULL OR blocked_until > NOW())"
            );
            $stmt->execute(['ip' => $ip, 'tid' => $tenantId]);
            return $stmt->fetch() !== false;
        } catch (\Throwable $e) {
            return false;
        }
    }
    
    /**
     * حظر IP تلقائياً (حسب المختبر — لا يُحظر إن كان في القائمة البيضاء)
     */
    public function blockIp(string $ip, int $durationHours = 24, string $reason = 'محاولات دخول فاشلة متعددة', ?int $tenantId = null): bool
    {
        if ($this->isIpWhitelisted($ip, $tenantId)) {
            return false;
        }
        $tenantId = $tenantId ?? (int)($_SESSION['tenant_id'] ?? 1);
        try {
            $blockedUntil = $durationHours > 0 ? date('Y-m-d H:i:s', time() + ($durationHours * 3600)) : null;
            $stmt = $this->db->prepare(
                "INSERT INTO blocked_ips (tenant_id, ip_address, blocked_until, reason, is_permanent) 
                 VALUES (:tid, :ip, :until, :reason, 0)
                 ON DUPLICATE KEY UPDATE 
                    blocked_until = VALUES(blocked_until),
                    reason = VALUES(reason),
                    attempts_count = attempts_count + 1"
            );
            return $stmt->execute([
                'tid' => $tenantId,
                'ip' => $ip,
                'until' => $blockedUntil,
                'reason' => $reason
            ]);
        } catch (\Throwable $e) {
            return false;
        }
    }

    public function attempt(string $username, string $password, ?int $tenantId = null, bool $rememberMe = false): bool
    {
        $tenantId = $tenantId ?? (int)($_SESSION['tenant_id'] ?? 1);
        
        $this->lastLoginFailure = null;

        // التحقق من حظر IP (حسب المختبر)
        $clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
        if ($this->isIpBlocked($clientIp, $tenantId)) {
            $this->lastLoginFailure = ['reason' => 'ip_blocked'];
            return false;
        }
        
        // الدخول باسم المستخدم أو رقم الهاتف
        $user = null;
        $stmt = $this->db->prepare('SELECT * FROM users WHERE username = :u AND is_active = 1 AND (tenant_id = :tid OR (tenant_id IS NULL AND :tid2 = 1))');
        $stmt->execute(['u' => $username, 'tid' => $tenantId, 'tid2' => $tenantId]);
        $user = $stmt->fetch();
        if (!$user) {
            $phoneNorm = preg_replace('/\D/', '', $username);
            if (strlen($phoneNorm) >= 7) {
                try {
                    $this->db->query('SELECT phone FROM users LIMIT 1');
                    $stmt = $this->db->prepare('SELECT * FROM users WHERE (phone = :p OR phone = :p2 OR REPLACE(REPLACE(phone, " ", ""), "-", "") = :p3) AND is_active = 1 AND (tenant_id = :tid OR (tenant_id IS NULL AND :tid2 = 1))');
                    $stmt->execute([
                        'p' => $username,
                        'p2' => $phoneNorm,
                        'p3' => $phoneNorm,
                        'tid' => $tenantId,
                        'tid2' => $tenantId
                    ]);
                    $user = $stmt->fetch();
                } catch (\Throwable $e) {}
            }
        }

        if (!$user) {
            $this->lastLoginFailure = ['reason' => 'user_not_found', 'username' => $username, 'tenant_id' => $tenantId];
            $this->recordFailedLoginAttempt($clientIp, $tenantId);
            return false;
        }

        if (!empty($user['locked_until']) && strtotime((string)$user['locked_until']) > time()) {
            $this->lastLoginFailure = ['reason' => 'locked'];
            return false;
        }

        if (!password_verify($password, $user['password_hash'])) {
            $this->lastLoginFailure = ['reason' => 'wrong_password', 'tenant_id' => $tenantId];
            $stmt = $this->db->prepare(
                'UPDATE users
                 SET failed_attempts = failed_attempts + 1,
                     locked_until = CASE
                         WHEN failed_attempts + 1 >= 5 THEN DATE_ADD(NOW(), INTERVAL 15 MINUTE)
                         ELSE locked_until
                     END
                 WHERE id = :id'
            );
            $stmt->execute(['id' => $user['id']]);
            
            // تسجيل محاولة فاشلة للـ IP (حسب المختبر)
            $this->recordFailedLoginAttempt($clientIp, $tenantId);
            return false;
        }

        $userTid = isset($user['tenant_id']) && $user['tenant_id'] !== null ? (int)$user['tenant_id'] : 1;
        if (!TenantPolicy::labAccessAllowed($this->db, $userTid)) {
            $this->lastLoginFailure = ['reason' => 'tenant_inactive', 'tenant_id' => $userTid];
            return false;
        }

        $_SESSION['user_id'] = (int)$user['id'];
        session_regenerate_id(true);
        $_SESSION['user_name'] = $user['full_name'];
        $_SESSION['role_id'] = (int)$user['role_id'];
        $_SESSION['tenant_id'] = isset($user['tenant_id']) && $user['tenant_id'] !== null ? (int)$user['tenant_id'] : 1;
        $_SESSION['user_branch_id'] = isset($user['branch_id']) && $user['branch_id'] !== null ? (int)$user['branch_id'] : null;
        $_SESSION['last_activity'] = time();

        $this->db
            ->prepare('UPDATE users SET last_login_at = NOW(), failed_attempts = 0, locked_until = NULL WHERE id = :id')
            ->execute(['id' => $user['id']]);

        if ($rememberMe) {
            $this->setRememberToken((int)$user['id']);
        }

        return true;
    }

    /**
     * إنشاء رمز تذكرني وحفظه في قاعدة البيانات والكوكي
     */
    private function setRememberToken(int $userId): void
    {
        try {
            $token = bin2hex(random_bytes(32));
            $hash = hash('sha256', $token);
            $expiresAt = date('Y-m-d H:i:s', time() + (30 * 24 * 3600)); // 30 يوم

            $this->db->prepare('DELETE FROM remember_tokens WHERE user_id = ?')->execute([$userId]);
            $this->db->prepare('INSERT INTO remember_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)')
                ->execute([$userId, $hash, $expiresAt]);

            $cookieValue = $userId . ':' . $token;
            setcookie('remember', $cookieValue, Session::cookieOptions(time() + (30 * 24 * 3600)));
        } catch (\Throwable $e) {
        }
    }

    private function clearRememberCookie(): void
    {
        setcookie('remember', '', Session::cookieOptions(time() - 3600));
    }

    /**
     * محاولة استعادة الجلسة من كوكي تذكرني
     */
    private function rememberFromCookie(): bool
    {
        $cookie = $_COOKIE['remember'] ?? '';
        if ($cookie === '' || strpos($cookie, ':') === false) {
            return false;
        }
        $parts = explode(':', $cookie, 2);
        $userId = (int)($parts[0] ?? 0);
        $token = $parts[1] ?? '';
        if ($userId <= 0 || strlen($token) !== 64) {
            return false;
        }
        try {
            $hash = hash('sha256', $token);
            $stmt = $this->db->prepare(
                'SELECT u.* FROM users u
                 JOIN remember_tokens rt ON rt.user_id = u.id
                 WHERE u.id = :uid AND rt.token_hash = :hash AND rt.expires_at > NOW() AND u.is_active = 1'
            );
            $stmt->execute(['uid' => $userId, 'hash' => $hash]);
            $user = $stmt->fetch();
            if (!$user) {
                $this->clearRememberCookie();
                return false;
            }
            $userTid = isset($user['tenant_id']) && $user['tenant_id'] !== null ? (int)$user['tenant_id'] : 1;
            if (!TenantPolicy::labAccessAllowed($this->db, $userTid)) {
                $this->clearRememberCookie();
                return false;
            }
            $_SESSION['user_id'] = (int)$user['id'];
            session_regenerate_id(true);
            $_SESSION['user_name'] = $user['full_name'];
            $_SESSION['role_id'] = (int)$user['role_id'];
            $_SESSION['tenant_id'] = isset($user['tenant_id']) && $user['tenant_id'] !== null ? (int)$user['tenant_id'] : 1;
            $_SESSION['user_branch_id'] = isset($user['branch_id']) && $user['branch_id'] !== null ? (int)$user['branch_id'] : null;
            $_SESSION['last_activity'] = time();
            // Rotate remember token on each successful cookie login.
            $this->setRememberToken((int)$user['id']);
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }
    
    /**
     * تسجيل محاولة دخول فاشلة للـ IP (حظر حسب المختبر)
     */
    private function recordFailedLoginAttempt(string $ip, ?int $tenantId = null): void
    {
        if (empty($ip)) {
            return;
        }
        $tenantId = $tenantId ?? (int)($_SESSION['tenant_id'] ?? 1);
        
        try {
            $config = require __DIR__ . '/../../config/config.php';
            $autoBlock = $config['security']['auto_block_suspicious_ips'] ?? false;
            $threshold = (int)($config['security']['suspicious_ip_threshold'] ?? 5);
            $durationHours = (int)($config['security']['ip_block_duration_hours'] ?? 24);
            
            if (!$autoBlock) {
                return;
            }
            
            // حساب عدد المحاولات الفاشلة في آخر ساعة (للمختبر الحالي إن وُجد tenant_id)
            $tenantCond = '';
            $params = ['ip' => $ip];
            try {
                $this->db->query('SELECT tenant_id FROM audit_log LIMIT 1');
                $tenantCond = ' AND (tenant_id = :tid OR tenant_id IS NULL)';
                $params['tid'] = $tenantId;
            } catch (\Throwable $e) {}
            $stmt = $this->db->prepare(
                "SELECT COUNT(*) FROM audit_log 
                 WHERE action = 'login_failed' 
                 AND ip_address = :ip 
                 AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR)" . $tenantCond
            );
            $stmt->execute($params);
            $attempts = (int)$stmt->fetchColumn();
            
            // إذا تجاوز العتبة، حظر IP لهذا المختبر فقط
            if ($attempts >= $threshold) {
                $this->blockIp($ip, $durationHours, "تجاوز {$threshold} محاولات دخول فاشلة في الساعة الأخيرة", $tenantId);
            }
        } catch (\Throwable $e) {
            // تجاهل الأخطاء في التسجيل
        }
    }

    public function loadPermissions(): void
    {
        if (!$this->check()) {
            return;
        }
        if (!empty($this->permissions)) {
            return;
        }
        if (isset($_SESSION['permissions']) && is_array($_SESSION['permissions'])) {
            $this->permissions = $_SESSION['permissions'];
            return;
        }
        $roleId = $_SESSION['role_id'] ?? null;
        if ($roleId === null) {
            $this->permissions = [];
            $_SESSION['permissions'] = [];
            return;
        }
        try {
            $stmt = $this->db->prepare(
                'SELECT p.code
                 FROM permissions p
                 JOIN role_permissions rp ON rp.permission_id = p.id
                 WHERE rp.role_id = :role_id'
            );
            $stmt->execute(['role_id' => $roleId]);
            $rows = $stmt->fetchAll();
            $codes = [];
            foreach ($rows as $row) {
                $codes[] = $row['code'];
            }
            $this->permissions = $codes;
            $_SESSION['permissions'] = $codes;
        } catch (\Throwable $e) {
            $this->permissions = [];
            $_SESSION['permissions'] = [];
        }
    }

    public function can(string $permission): bool
    {
        if (($this->check()) && (($_SESSION['role_id'] ?? null) === 1)) {
            return true;
        }
        $this->loadPermissions();
        return in_array($permission, $this->permissions, true);
    }

    public function logout(): void
    {
        Session::start();

        $userId = $_SESSION['user_id'] ?? null;
        if ($userId) {
            try {
                $this->db->prepare('DELETE FROM remember_tokens WHERE user_id = ?')->execute([$userId]);
            } catch (\Throwable $e) {}
        }
        $this->clearRememberCookie();
        $_SESSION = [];
        session_destroy();
    }

    public function check(): bool
    {
        Session::start();

        if (!isset($_SESSION['user_id'])) {
            if ($this->rememberFromCookie()) {
                return true;
            }
            return false;
        }

        $now = time();
        $timeoutSeconds = 30 * 60;

        if (isset($_SESSION['last_activity']) && ($now - (int)$_SESSION['last_activity']) > $timeoutSeconds) {
            $_SESSION = [];
            session_regenerate_id(true);
            return false;
        }

        $_SESSION['last_activity'] = $now;

        return true;
    }

    public function userId(): ?int
    {
        if (!$this->check()) {
            return null;
        }

        return (int)$_SESSION['user_id'];
    }

    public function branchId(): ?int
    {
        if (!$this->check()) {
            return null;
        }
        $bid = $_SESSION['user_branch_id'] ?? null;
        return $bid !== null ? (int)$bid : null;
    }

    /** هل المستخدم مُقيّد برؤية الأسماء فقط (إخفاء النتائج) */
    public function maskResults(): bool
    {
        return $this->can('data.mask_results');
    }

    /** الفروع التي يمكن للمستخدم العمل فيها */
    public function availableBranches(): array
    {
        if (!$this->check()) return [];
        if (isset($_SESSION['available_branches']) && is_array($_SESSION['available_branches'])) {
            return $_SESSION['available_branches'];
        }
        if (($_SESSION['role_id'] ?? 0) === 1) {
            try {
                $tid = (int)($_SESSION['tenant_id'] ?? 1);
                $stmt = $this->db->prepare('SELECT id, name FROM branches WHERE is_active = 1 AND tenant_id = ? ORDER BY name');
                $stmt->execute([$tid]);
                $rows = $stmt->fetchAll();
                $_SESSION['available_branches'] = array_map(fn($r) => ['id'=>(int)$r['id'],'name'=>$r['name']], $rows);
                return $_SESSION['available_branches'];
            } catch (\Throwable $e) { return []; }
        }
        $uid = $this->userId();
        if (!$uid) return [];
        try {
            $stmt = $this->db->prepare(
                'SELECT u.branch_id FROM users u WHERE u.id = :uid AND u.branch_id IS NOT NULL
                 UNION
                 SELECT ub.branch_id FROM user_branches ub WHERE ub.user_id = :uid2'
            );
            $stmt->execute(['uid' => $uid, 'uid2' => $uid]);
            $rows = $stmt->fetchAll();
            $ids = [];
            foreach ($rows as $r) {
                if (!empty($r['branch_id'])) $ids[(int)$r['branch_id']] = true;
            }
            $ids = array_keys($ids);
            $names = [];
            $tid = (int)($_SESSION['tenant_id'] ?? 1);
            if (!empty($ids)) {
                $ph = implode(',', array_fill(0, count($ids), '?'));
                $stmt = $this->db->prepare("SELECT id, name FROM branches WHERE id IN ($ph) AND is_active = 1 AND tenant_id = ?");
                $stmt->execute(array_merge($ids, [$tid]));
                foreach ($stmt->fetchAll() as $b) {
                    $names[] = ['id' => (int)$b['id'], 'name' => $b['name']];
                }
            }
            $_SESSION['available_branches'] = $names;
            return $names;
        } catch (\Throwable $e) {
            return [];
        }
    }
}
