<?php

use Core\Model;

/**
 * UserAuthModel — handles user authentication lookups, OTP secrets, and login tracking
 */
class UserAuthModel extends Model
{
    protected $table = 'users';

    public function findByEmail(string $email): ?array
    {
        return $this->queryOne(
            "SELECT u.*, o.name AS org_name, o.locale AS org_locale, o.currency AS org_currency,
                    o.timezone AS org_timezone, o.is_active AS org_is_active,
                    o.subscription_status, o.trial_ends_at, o.plan_id
             FROM users u
             LEFT JOIN organizations o ON u.org_id = o.id
             WHERE u.email = ?",
            [$email]
        );
    }

    public function findByIdWithOrg(int $id): ?array
    {
        return $this->queryOne(
            "SELECT u.*, o.name AS org_name, o.locale AS org_locale, o.currency AS org_currency,
                    o.timezone AS org_timezone, o.slug AS org_slug, o.logo AS org_logo,
                    o.subscription_status, o.trial_ends_at, o.plan_id
             FROM users u
             LEFT JOIN organizations o ON u.org_id = o.id
             WHERE u.id = ?",
            [$id]
        );
    }

    public function updateLastLogin(int $id): bool
    {
        return $this->update($id, ['last_login_at' => date('Y-m-d H:i:s')]);
    }

    public function getOtpSecret(int $userId): ?array
    {
        return $this->queryOne(
            "SELECT * FROM user_otp_secrets WHERE user_id = ?",
            [$userId]
        );
    }

    public function saveOtpSecret(int $userId, string $secret, array $backupCodes): bool
    {
        $existing = $this->getOtpSecret($userId);
        if ($existing) {
            return $this->execute(
                "UPDATE user_otp_secrets SET secret = ?, backup_codes = ?, updated_at = NOW() WHERE user_id = ?",
                [$secret, json_encode($backupCodes), $userId]
            );
        }
        return $this->execute(
            "INSERT INTO user_otp_secrets (user_id, secret, is_enabled, backup_codes, created_at, updated_at)
             VALUES (?, ?, 0, ?, NOW(), NOW())",
            [$userId, $secret, json_encode($backupCodes)]
        );
    }

    public function enableOtp(int $userId): bool
    {
        return $this->execute(
            "UPDATE user_otp_secrets SET is_enabled = 1, updated_at = NOW() WHERE user_id = ?",
            [$userId]
        );
    }

    public function disableOtp(int $userId): bool
    {
        return $this->execute(
            "UPDATE user_otp_secrets SET is_enabled = 0, updated_at = NOW() WHERE user_id = ?",
            [$userId]
        );
    }

    public function verifyBackupCode(int $userId, string $code): bool
    {
        $secret = $this->getOtpSecret($userId);
        if (!$secret) return false;

        $codes = json_decode($secret['backup_codes'], true) ?? [];
        $code  = strtoupper(trim($code));

        if (in_array($code, $codes)) {
            $codes = array_values(array_filter($codes, fn($c) => $c !== $code));
            $this->execute(
                "UPDATE user_otp_secrets SET backup_codes = ?, updated_at = NOW() WHERE user_id = ?",
                [json_encode($codes), $userId]
            );
            return true;
        }
        return false;
    }

    public function incrementLoginAttempts(string $email): void
    {
        $this->execute(
            "INSERT INTO login_attempts (email, ip_address, attempted_at, success) VALUES (?, ?, NOW(), 0)",
            [$email, $_SERVER['REMOTE_ADDR'] ?? '']
        );
    }

    public function resetLoginAttempts(string $email): void
    {
        // Log successful attempt
        $this->execute(
            "INSERT INTO login_attempts (email, ip_address, attempted_at, success) VALUES (?, ?, NOW(), 1)",
            [$email, $_SERVER['REMOTE_ADDR'] ?? '']
        );
    }

    public function isLockedOut(string $email): bool
    {
        $result = $this->queryOne(
            "SELECT COUNT(*) as cnt FROM login_attempts
             WHERE email = ? AND success = 0 AND attempted_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE)",
            [$email]
        );
        return ($result['cnt'] ?? 0) >= 5;
    }

    // ─── User Management ───────────────────────────────────────────────

    public function getUsersForOrg(?int $orgId, array $filters = [], int $page = 1, int $perPage = 20): array
    {
        $where  = [];
        $params = [];
        if ($orgId !== null) {
            $where[]  = 'u.org_id = ?';
            $params[] = $orgId;
        }

        if (!empty($filters['search'])) {
            $where[]  = "(u.first_name LIKE ? OR u.last_name LIKE ? OR u.email LIKE ?)";
            $s        = '%' . $filters['search'] . '%';
            $params[] = $s;
            $params[] = $s;
            $params[] = $s;
        }
        if (isset($filters['is_active'])) {
            $where[]  = 'u.is_active = ?';
            $params[] = (int) $filters['is_active'];
        }
        if (!empty($filters['entity_type'])) {
            $where[]  = 'u.entity_type = ?';
            $params[] = $filters['entity_type'];
        }
        if (!empty($filters['role_id'])) {
            $where[]  = 'ur.role_id = ?';
            $params[] = $filters['role_id'];
        }

        $whereStr = $where ? implode(' AND ', $where) : '1=1';
        $offset   = ($page - 1) * $perPage;

        $total = $this->queryOne(
            "SELECT COUNT(DISTINCT u.id) as cnt FROM users u
             LEFT JOIN user_roles ur ON u.id = ur.user_id
             WHERE {$whereStr}",
            $params
        )['cnt'] ?? 0;

        $data = $this->query(
            "SELECT u.*, GROUP_CONCAT(DISTINCT r.name SEPARATOR ', ') as roles, o.name as org_name
             FROM users u
             LEFT JOIN user_roles ur ON u.id = ur.user_id
             LEFT JOIN roles r ON ur.role_id = r.id
             LEFT JOIN organizations o ON u.org_id = o.id
             WHERE {$whereStr}
             GROUP BY u.id
             ORDER BY u.created_at DESC
             LIMIT {$perPage} OFFSET {$offset}",
            $params
        );

        return [
            'data'    => $data,
            'total'   => (int) $total,
            'pages'   => (int) ceil($total / $perPage),
            'current' => $page,
            'perPage' => $perPage,
        ];
    }

    public function createUser(array $data): int|false
    {
        $data['password'] = password_hash($data['password'], PASSWORD_BCRYPT);
        return $this->create($data);
    }

    public function getUserRoles(int $userId): array
    {
        return $this->query(
            "SELECT r.* FROM roles r
             INNER JOIN user_roles ur ON r.id = ur.role_id
             WHERE ur.user_id = ?",
            [$userId]
        );
    }

    public function getUserPermissions(int $userId): array
    {
        return $this->query(
            "SELECT DISTINCT CONCAT(p.module, '.', p.action) as permission, p.module, p.action
             FROM permissions p
             INNER JOIN role_permissions rp ON p.id = rp.permission_id
             INNER JOIN user_roles ur ON rp.role_id = ur.role_id
             WHERE ur.user_id = ?",
            [$userId]
        );
    }

    public function assignRole(int $userId, int $roleId): bool
    {
        // Avoid duplicate
        $existing = $this->queryOne(
            "SELECT id FROM user_roles WHERE user_id = ? AND role_id = ?",
            [$userId, $roleId]
        );
        if ($existing) return true;

        return $this->execute(
            "INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)",
            [$userId, $roleId]
        );
    }

    public function removeRole(int $userId, int $roleId): bool
    {
        return $this->execute(
            "DELETE FROM user_roles WHERE user_id = ? AND role_id = ?",
            [$userId, $roleId]
        );
    }
}
