const { getClientToken } = require('./graph.service');
const Database = require('better-sqlite3');
const path = require('path');
const fs = require('fs');

const DB_PATH = process.env.DATABASE_PATH || './database.sqlite';

async function syncEntraProfiles() {
    const db = new Database(DB_PATH);
    const startedAt = new Date().toISOString();

    // Log starten
    const logId = db.prepare(`
        INSERT INTO cron_logs (job_name, started_at, status) VALUES (?, ?, 'running')
    `).run('entra_profile_sync', startedAt).lastInsertRowid;

    try {
        const token = await getClientToken();
        const users = db.prepare(`SELECT id, azure_id, username FROM users WHERE azure_id IS NOT NULL AND azure_id != ''`).all();

        let synced = 0, errors = 0, photos = 0;

        for (const user of users) {
            try {
                // Profildaten holen
                const profileRes = await fetch(
                    `https://graph.microsoft.com/v1.0/users/${user.azure_id}?$select=jobTitle,department,mobilePhone,businessPhones,officeLocation,employeeHireDate,displayName,mail,givenName,surname`,
                    { headers: { Authorization: `Bearer ${token}` } }
                );

                if (profileRes.ok) {
                    const p = await profileRes.json();
                    const phone = p.mobilePhone || (p.businessPhones?.[0]) || null;

                    db.prepare(`
                        UPDATE users SET
                            position = COALESCE(?, position),
                            department = COALESCE(?, department),
                            phone = COALESCE(?, phone),
                            location = COALESCE(?, location),
                            joined_date = COALESCE(?, joined_date),
                            first_name = COALESCE(?, first_name),
                            last_name = COALESCE(?, last_name)
                        WHERE id = ?
                    `).run(
                        p.jobTitle || null,
                        p.department || null,
                        phone,
                        p.officeLocation || null,
                        p.employeeHireDate ? p.employeeHireDate.split('T')[0] : null,
                        p.givenName || null,
                        p.surname || null,
                        user.id
                    );
                    synced++;
                }

                // Foto holen
                const photoRes = await fetch(
                    `https://graph.microsoft.com/v1.0/users/${user.azure_id}/photo/$value`,
                    { headers: { Authorization: `Bearer ${token}` } }
                );

                if (photoRes.ok) {
                    const avatarDir = path.join(process.env.UPLOAD_PATH || path.join(__dirname, '../../uploads'), 'avatars');
                    if (!fs.existsSync(avatarDir)) fs.mkdirSync(avatarDir, { recursive: true });

                    const buffer = Buffer.from(await photoRes.arrayBuffer());
                    const filename = `${user.azure_id}.jpg`;
                    fs.writeFileSync(path.join(avatarDir, filename), buffer);

                    db.prepare(`UPDATE users SET avatar_url = ? WHERE id = ?`)
                        .run(`/uploads/avatars/${filename}`, user.id);
                    photos++;
                }

            } catch (err) {
                console.error(`[Entra Sync] Fehler bei User ${user.username}:`, err.message);
                errors++;
            }
        }

        // ── Schritt 2: Neue Entra-User → IT Nexus anlegen ──────────────────────
        let newUsers = 0;
        try {
            const allEntraUsersRes = await fetch(
                'https://graph.microsoft.com/v1.0/users?$select=id,displayName,givenName,surname,mail,userPrincipalName,jobTitle,department,mobilePhone,businessPhones,officeLocation,employeeHireDate&$top=999',
                { headers: { Authorization: `Bearer ${token}` } }
            );
            const allEntraUsers = await allEntraUsersRes.json();

            for (const eu of (allEntraUsers.value || [])) {
                if (!eu.mail && !eu.userPrincipalName) continue;
                const email = (eu.mail || eu.userPrincipalName).toLowerCase().trim();

                // Prüfe ob User schon existiert (per azure_id oder email)
                const existing = db.prepare('SELECT id FROM users WHERE azure_id=? OR email=?').get(eu.id, email);
                if (existing) {
                    // Falls azure_id noch nicht gesetzt, verknüpfen
                    if (!db.prepare('SELECT azure_id FROM users WHERE id=?').get(existing.id)?.azure_id) {
                        db.prepare('UPDATE users SET azure_id=? WHERE id=?').run(eu.id, existing.id);
                    }
                    continue;
                }

                // Neuen User anlegen
                const benutzerRole = db.prepare("SELECT id FROM roles WHERE name='benutzer'").get();
                if (!benutzerRole) continue;

                let username = email.split('@')[0].replace(/[^a-z0-9._-]/gi, '').toLowerCase();
                let suffix = 1;
                while (db.prepare('SELECT id FROM users WHERE username=?').get(username)) {
                    username = `${email.split('@')[0].replace(/[^a-z0-9._-]/gi, '').toLowerCase()}${suffix++}`;
                }

                db.prepare(`INSERT INTO users (username, email, password_hash, role_id, first_name, last_name, is_active, must_change_password, azure_id, department, position, phone, location, joined_date) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(
                    username, email, 'AZURE_SSO_NO_PASSWORD', benutzerRole.id,
                    eu.givenName || null, eu.surname || null, 1, 0, eu.id,
                    eu.department || null, eu.jobTitle || null,
                    eu.mobilePhone || (eu.businessPhones?.[0]) || null,
                    eu.officeLocation || null,
                    eu.employeeHireDate ? eu.employeeHireDate.split('T')[0] : null
                );
                newUsers++;
            }
            console.log(`[Entra Sync] Schritt 2: ${newUsers} neue User angelegt`);
        } catch (err) {
            console.error('[Entra Sync] Schritt 2 (neue User) fehlgeschlagen:', err.message);
            errors++;
        }

        // ── Schritt 3: Intune-Geräte → Assets sync ──────────────────────────────
        let newAssets = 0, updatedAssets = 0;
        try {
            const intuneRes = await fetch(
                'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=id,deviceName,serialNumber,model,manufacturer,operatingSystem,osVersion,userDisplayName,userPrincipalName,lastSyncDateTime,managementState,complianceState&$top=999',
                { headers: { Authorization: `Bearer ${token}` } }
            );
            const intuneDevices = await intuneRes.json();

            for (const device of (intuneDevices.value || [])) {
                if (!device.serialNumber || device.serialNumber === 'unknown') continue;

                // Asset-Typ bestimmen
                let assetType = 'sonstiges';
                const name = (device.deviceName || '').toLowerCase();
                const os = (device.operatingSystem || '').toLowerCase();
                if (os.includes('windows') && (name.includes('nb') || name.includes('laptop') || name.includes('book'))) assetType = 'Notebook';
                else if (os.includes('windows')) assetType = 'Notebook'; // Default Windows = Notebook

                // Benutzer finden
                let assignedUserId = null;
                if (device.userPrincipalName) {
                    const assignedUser = db.prepare('SELECT id FROM users WHERE email=? OR username=?')
                        .get(device.userPrincipalName.toLowerCase(), device.userPrincipalName.split('@')[0].toLowerCase());
                    assignedUserId = assignedUser?.id || null;
                }

                const existingAsset = db.prepare('SELECT id, status FROM assets WHERE serial_number=? OR name=?').get(device.serialNumber, device.deviceName);

                if (existingAsset) {
                    // Bestehend updaten (OS, letzter Sync)
                    db.prepare('UPDATE assets SET os=?, last_agent_sync=?, updated_at=CURRENT_TIMESTAMP WHERE id=?')
                        .run(device.operatingSystem ? `${device.operatingSystem} ${device.osVersion || ''}`.trim() : null, device.lastSyncDateTime, existingAsset.id);
                    updatedAssets++;
                } else {
                    // Neues Asset anlegen
                    db.prepare(`INSERT INTO assets (name, type, serial_number, model, manufacturer, os, status, department, assigned_to_user_id, last_agent_sync, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`).run(
                        device.deviceName || device.serialNumber,
                        assetType,
                        device.serialNumber,
                        device.model || null,
                        device.manufacturer || null,
                        device.operatingSystem ? `${device.operatingSystem} ${device.osVersion || ''}`.trim() : null,
                        assignedUserId ? 'zugewiesen' : 'verfuegbar',
                        'IT',
                        assignedUserId,
                        device.lastSyncDateTime
                    );
                    newAssets++;
                }
            }
            console.log(`[Entra Sync] Schritt 3: ${newAssets} neue Assets, ${updatedAssets} aktualisiert`);
        } catch (err) {
            console.error('[Entra Sync] Schritt 3 (Intune Assets) fehlgeschlagen:', err.message);
            errors++;
        }

        // ── Schritt 4: Entra-Geräte (Hybrid Join + Azure AD Join) → Assets sync ──
        try {
            const entraDevRes = await fetch(
                'https://graph.microsoft.com/v1.0/devices?$select=id,displayName,operatingSystem,operatingSystemVersion,manufacturer,model,deviceId,approximateLastSignInDateTime,registrationDateTime,trustType&$top=999',
                { headers: { Authorization: `Bearer ${token}` } }
            );
            const entraDevices = await entraDevRes.json();

            for (const device of (entraDevices.value || [])) {
                if (!device.displayName) continue;
                // Nur Windows-Geräte (Hybrid/Azure AD joined)
                const os = (device.operatingSystem || '').toLowerCase();
                if (!os.includes('windows')) continue;

                // Prüfe ob Asset bereits existiert (per Name)
                const existingByName = db.prepare('SELECT id FROM assets WHERE name=?').get(device.displayName);
                if (existingByName) {
                    // OS updaten falls nötig
                    db.prepare('UPDATE assets SET os=?, updated_at=CURRENT_TIMESTAMP WHERE id=?')
                        .run(device.operatingSystem ? `${device.operatingSystem} ${device.operatingSystemVersion || ''}`.trim() : null, existingByName.id);
                    updatedAssets++;
                    continue;
                }

                // Typ bestimmen
                let assetType = 'Notebook';
                const name = device.displayName.toLowerCase();
                if (name.includes('srv') || name.includes('server')) assetType = 'Other';

                db.prepare(`INSERT INTO assets (name, type, model, manufacturer, os, status, department, created_at, updated_at) VALUES (?,?,?,?,?,?,?,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`).run(
                    device.displayName,
                    assetType,
                    device.model || null,
                    device.manufacturer || null,
                    device.operatingSystem ? `${device.operatingSystem} ${device.operatingSystemVersion || ''}`.trim() : null,
                    'verfuegbar',
                    'IT'
                );
                newAssets++;
            }
            console.log(`[Entra Sync] Schritt 4: Entra-Geräte sync abgeschlossen`);
        } catch (err) {
            console.error('[Entra Sync] Schritt 4 (Entra Devices) fehlgeschlagen:', err.message);
            errors++;
        }

        const message = `${synced} Profile sync, ${photos} Fotos, ${newUsers} neue User, ${newAssets} neue Assets, ${updatedAssets} Assets aktualisiert, ${errors} Fehler`;
        db.prepare(`UPDATE cron_logs SET status='success', finished_at=?, message=?, details=? WHERE id=?`)
            .run(new Date().toISOString(), message, JSON.stringify({ synced, photos, newUsers, newAssets, updatedAssets, errors, total: users.length }), logId);

        console.log(`[Entra Sync] ${message}`);
        return { synced, photos, newUsers, newAssets, updatedAssets, errors };

    } catch (err) {
        db.prepare(`UPDATE cron_logs SET status='error', finished_at=?, message=? WHERE id=?`)
            .run(new Date().toISOString(), err.message, logId);
        throw err;
    } finally {
        db.close();
    }
}

module.exports = { syncEntraProfiles };
