<?php
declare(strict_types=1);

/**
 * Inserts a fresh LDAP-backed user on first login (JIT) or refreshes an
 * existing one (username, email casing, role, last-login). The local password
 * column receives an unverifiable marker so the bcrypt path can never grant
 * access — only the LDAP filter can.
 */
class LdapProvisioner
{
    /** Marker stored in users.password for LDAP-managed accounts. */
    public const LDAP_PASSWORD_MARKER = '!ldap-no-local-password!';

    /**
     * Returns the fresh users-row or null on hard insert/update failure.
     * $entryAttrs is an ldap_get_attributes() result.
     */
    public static function syncUser(
        string $email,
        string $userDn,
        array $entryAttrs,
        bool $isAdmin,
        ?array $existing,
        array $cfg
    ): ?array {
        $db       = Database::getInstance();
        $username = self::firstAttr($entryAttrs, $cfg['attr_username']) ?: self::usernameFromEmail($email);
        $mailAttr = self::firstAttr($entryAttrs, $cfg['attr_email'])    ?: $email;
        $emailLc  = strtolower(trim($mailAttr));
        $role     = $isAdmin ? 'admin' : 'user';
        $now      = time();

        if ($existing) {
            $userId = (int)$existing['id'];
            // Takeover from local provider: the admin opted in via setting.
            // We swap the password marker so the local path is dead from now on.
            try {
                $db->execute(
                    'UPDATE users
                       SET username       = ?,
                           email          = ?,
                           role           = ?,
                           auth_provider  = ?,
                           external_id    = ?,
                           password       = ?,
                           is_active      = 1,
                           ldap_synced_at = ?,
                           last_login     = CURRENT_TIMESTAMP
                     WHERE id = ?',
                    [$username, $emailLc, $role, 'ldap', $userDn, self::LDAP_PASSWORD_MARKER, $now, $userId]
                );
            } catch (\Throwable $e) {
                error_log('[Plenvo LDAP] update failed for user ' . $userId . ': ' . $e->getMessage());
                return null;
            }
            return $db->fetch('SELECT * FROM users WHERE id = ?', [$userId]);
        }

        // JIT-provision. Default language and timezone from app settings so the
        // first email/notification renders correctly.
        $lang = (string)Helper::getSetting('default_language', 'de');
        $tz   = (string)Helper::getSetting('default_timezone', 'Europe/Berlin');

        try {
            $db->execute(
                'INSERT INTO users (username, email, password, role, language, timezone, theme, is_active, auth_provider, external_id, ldap_synced_at, last_login)
                 VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, CURRENT_TIMESTAMP)',
                [$username, $emailLc, self::LDAP_PASSWORD_MARKER, $role, $lang, $tz, 'light', 'ldap', $userDn, $now]
            );
        } catch (\Throwable $e) {
            error_log('[Plenvo LDAP] JIT insert failed for ' . Helper::maskEmail($emailLc) . ': ' . $e->getMessage());
            return null;
        }
        $newId = (int)$db->lastInsertId();
        SecurityLog::log('ldap_user_provisioned', SecurityLog::SEV_INFO, [
            'email_hint' => Helper::maskEmail($emailLc),
            'role'       => $role,
        ], $newId);

        return $db->fetch('SELECT * FROM users WHERE id = ?', [$newId]);
    }

    /** Pulls the first value of a (case-insensitively matched) LDAP attribute. */
    private static function firstAttr(array $entryAttrs, string $name): string
    {
        if ($name === '') return '';
        $lcName = strtolower($name);
        foreach ($entryAttrs as $key => $val) {
            if (!is_string($key)) continue;
            if (strtolower($key) === $lcName) {
                if (is_array($val) && isset($val[0])) return (string)$val[0];
                if (is_string($val)) return $val;
            }
        }
        return '';
    }

    private static function usernameFromEmail(string $email): string
    {
        $at = strpos($email, '@');
        return $at !== false ? substr($email, 0, $at) : $email;
    }
}
