const MonitoringAgent = require('../models/MonitoringAgent');
const Asset = require('../models/Asset');
const { asyncHandler } = require('../middleware/errorHandler');
const { getPendingCommands } = require('./patch.controller');
const { getDatabase } = require('../config/database');
const { getForAgent } = require('./announcement.controller');
const { validateAgentKey } = require('../utils/agentAuth');
const crypto = require('crypto');

// Code-seitiger Fallback falls AGENT_VERSION env-Var fehlt — bei jedem Agent-Release mit hochziehen!
// (War zuvor an mehreren Stellen hartcodiert auf alte Versionen wie '2.0.0'/'1.2.4' — echter
// Downgrade-Bug für Agents ohne Patch-Gruppe oder ohne ?hostname-Parameter.)
const FALLBACK_AGENT_VERSION = '2.8.0';

// Vergleicht zwei "x.y.z"-Versionsstrings. >0 wenn a>b, <0 wenn a<b, 0 wenn gleich.
function compareVersions(a, b) {
    const pa = String(a || '0').split('.').map(n => parseInt(n, 10) || 0);
    const pb = String(b || '0').split('.').map(n => parseInt(n, 10) || 0);
    for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
        const diff = (pa[i] || 0) - (pb[i] || 0);
        if (diff !== 0) return diff;
    }
    return 0;
}

// Nie eine ältere Version als die aktuell installierte vorschlagen/ausliefern — verhindert
// Downgrades durch fehlerhafte ENV-Konfiguration, falsch gesetzte Gruppen-Zielversionen o.ä.
function clampToNotOlder(targetVersion, currentVersion) {
    if (!currentVersion) return targetVersion;
    return compareVersions(targetVersion, currentVersion) < 0 ? currentVersion : targetVersion;
}

// POST /api/monitoring/enroll — Agent tauscht geteilten Bootstrap-Key gegen individuellen Per-Device-Key.
// Idempotent: ein bereits enrollter Agent bekommt seinen bestehenden Key einfach erneut zurück.
const enroll = asyncHandler(async (req, res) => {
    const agentKey = req.headers['x-agent-key'];
    const { hostname } = req.body;
    if (!hostname) return res.status(400).json({ status: 'error', message: 'hostname required' });

    const db = getDatabase();
    const existing = db.prepare('SELECT id, agent_key FROM monitoring_agents WHERE hostname = ?').get(hostname);

    const validBootstrap = agentKey === process.env.AGENT_API_KEY;
    const validExisting = existing?.agent_key && agentKey === existing.agent_key;
    if (!validBootstrap && !validExisting) {
        return res.status(401).json({ status: 'error', message: 'Invalid agent key' });
    }

    let key = existing?.agent_key;
    if (!key) {
        key = 'itx-' + crypto.randomBytes(24).toString('hex');
        if (existing) {
            db.prepare('UPDATE monitoring_agents SET agent_key = ? WHERE id = ?').run(key, existing.id);
        } else {
            db.prepare('INSERT INTO monitoring_agents (hostname, agent_key) VALUES (?, ?)').run(hostname, key);
        }
        console.log(`[Enroll] Neuer Per-Device-Key ausgestellt für ${hostname}`);
    }
    res.json({ status: 'success', data: { agent_key: key } });
});

function extractManufacturer(modelStr) {
    if (!modelStr) return null;
    const known = ['HP', 'Dell', 'Lenovo', 'Apple', 'Asus', 'Acer', 'Microsoft', 'Samsung', 'Toshiba', 'Fujitsu', 'Panasonic', 'Getac'];
    const upper = modelStr.toUpperCase();
    for (const b of known) {
        if (upper.startsWith(b.toUpperCase())) return b;
    }
    return modelStr.trim().split(/\s+/)[0] || null;
}

function syncAgentToAsset(data) {
    try {
        const db = getDatabase();
        const hostname = data.hostname;
        if (!hostname) return;

        let asset = Asset.getByName(hostname);

        // Benutzer anhand last_user (Username) suchen
        let assignedUserId = null;
        if (data.last_user) {
            const cleanUser = data.last_user.replace(/^[^\\]+\\/, ''); // Domain\User → User
            const userRow = db.prepare('SELECT id FROM users WHERE LOWER(username) = LOWER(?) AND is_active = 1').get(cleanUser);
            if (userRow) assignedUserId = userRow.id;
        }

        const syncData = {
            serial_number: data.hardware_serial || undefined,
            os: data.os_name || data.os || undefined,
            ip_address: data.ip_address || undefined,
            last_agent_sync: new Date().toISOString(),
        };
        if (assignedUserId) {
            syncData.assigned_to_user_id = assignedUserId;
            syncData.status = 'zugewiesen';
        }

        if (asset) {
            // Hersteller nur setzen wenn noch nicht gesetzt
            if (!asset.manufacturer && asset.model) {
                syncData.manufacturer = extractManufacturer(asset.model);
            }
            Asset.update(asset.id, syncData, null);
        } else {
            // Neues Asset anlegen
            const manufacturer = data.hardware_serial ? null : null; // Modell noch unbekannt
            Asset.create({
                name: hostname,
                type: 'Notebook',
                serial_number: data.hardware_serial || null,
                model: null,
                status: 'zugewiesen',
                os: data.os_name || data.os || null,
                ip_address: data.ip_address || null,
                manufacturer: null,
                assigned_to_user_id: assignedUserId,
                department: 'IT',
                last_agent_sync: new Date().toISOString(),
                created_by_user_id: null,
            });
            console.log(`[AgentSync] Neues Asset erstellt: ${hostname}`);
        }
    } catch (err) {
        console.error('[AgentSync] Fehler beim Asset-Sync:', err.message);
    }
}

// POST /api/monitoring/checkin  — called by agent (no JWT, uses API key)
const checkin = asyncHandler(async (req, res) => {
    const agentKey = req.headers['x-agent-key'];
    const { hostname } = req.body;
    if (!hostname) {
        return res.status(400).json({ status: 'error', message: 'hostname required' });
    }
    if (!validateAgentKey(agentKey, hostname)) {
        return res.status(401).json({ status: 'error', message: 'Invalid agent key' });
    }

    const agent = MonitoringAgent.upsert(req.body);

    // Asset-Sync im Hintergrund (nicht blockend)
    setImmediate(() => syncAgentToAsset(req.body));

    // Pending patch commands für diesen Agenten zurückgeben
    const { pending, running } = getPendingCommands(agent.id);

    // Zielversion: Gruppen-spezifisch (staged rollout) oder globaler Default
    const GLOBAL_VERSION = process.env.AGENT_VERSION || FALLBACK_AGENT_VERSION;
    let targetVersion = GLOBAL_VERSION;
    try {
        const db = getDatabase();
        const groupRow = db.prepare(`
            SELECT pg.target_agent_version
            FROM patch_agent_groups pag
            JOIN patch_groups pg ON pg.id = pag.group_id
            WHERE pag.agent_id = ?
            ORDER BY pg.sort_order ASC
            LIMIT 1
        `).get(agent.id);
        if (groupRow?.target_agent_version) targetVersion = groupRow.target_agent_version;
    } catch { /* kein Gruppe zugewiesen → global */ }

    targetVersion = clampToNotOlder(targetVersion, req.body.agent_version);

    const announcements = getForAgent(agent.id);

    res.json({
        status: 'success',
        data: agent,
        commands: pending,
        running_commands: running,
        agent_version: targetVersion,
        announcements,
    });
});

// GET /api/monitoring  — list all agents (requires JWT)
const getAll = asyncHandler(async (req, res) => {
    MonitoringAgent.markOffline();
    const agents = MonitoringAgent.getAll().map(a => ({
        ...a,
        installed_software: a.installed_software ? JSON.parse(a.installed_software) : []
    }));
    res.json({ status: 'success', data: agents });
});

// POST /api/monitoring/announcements-poll — leichter Poll nur für Ankündigungen
const announcementsPoll = asyncHandler(async (req, res) => {
    const agentKey = req.headers['x-agent-key'];
    const { hostname } = req.body;
    if (!hostname) return res.status(400).json({ error: 'hostname required' });
    if (!validateAgentKey(agentKey, hostname)) {
        return res.status(401).json({ error: 'Unauthorized' });
    }
    const agent = MonitoringAgent.getByHostname(hostname);
    if (!agent) return res.json({ announcements: [] });
    const announcements = getForAgent(agent.id);
    res.json({ announcements });
});

// GET /api/monitoring/statistics
const getStatistics = asyncHandler(async (req, res) => {
    MonitoringAgent.markOffline();
    const stats = MonitoringAgent.getStatistics();
    res.json({ status: 'success', data: stats });
});

// GET /api/monitoring/:id
const getById = asyncHandler(async (req, res) => {
    const agent = MonitoringAgent.getById(req.params.id);
    if (!agent) return res.status(404).json({ status: 'error', message: 'Agent not found' });
    res.json({
        status: 'success',
        data: { ...agent, installed_software: agent.installed_software ? JSON.parse(agent.installed_software) : [] }
    });
});

// DELETE /api/monitoring/:id
const deleteAgent = asyncHandler(async (req, res) => {
    const deleted = MonitoringAgent.delete(req.params.id);
    if (!deleted) return res.status(404).json({ status: 'error', message: 'Agent not found' });
    res.json({ status: 'success', message: 'Agent removed' });
});

// GET /api/monitoring/agent-script — agent downloads its own update (X-Agent-Key auth)
const downloadScript = asyncHandler(async (req, res) => {
    const agentKey = req.headers['x-agent-key'];
    if (!agentKey || agentKey !== process.env.AGENT_API_KEY) {
        return res.status(401).json({ status: 'error', message: 'Invalid agent key' });
    }
    const path = require('path');
    const fs = require('fs');
    const scriptPath = path.join(__dirname, '../../agent/it-nexus-agent.ps1');
    if (!fs.existsSync(scriptPath)) {
        return res.status(404).json({ status: 'error', message: 'Script nicht gefunden' });
    }
    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    res.setHeader('X-Agent-Version', process.env.AGENT_VERSION || FALLBACK_AGENT_VERSION);
    res.sendFile(scriptPath);
});

// GET /api/monitoring/ann-watcher — agent downloads announcement watcher script
const downloadAnnWatcher = asyncHandler(async (req, res) => {
    const agentKey = req.headers['x-agent-key'];
    if (!agentKey || agentKey !== process.env.AGENT_API_KEY) {
        return res.status(401).json({ status: 'error', message: 'Invalid agent key' });
    }
    const path = require('path');
    const fs = require('fs');
    const scriptPath = path.join(__dirname, '../../agent/ann-watcher.ps1');
    if (!fs.existsSync(scriptPath)) {
        return res.status(404).json({ status: 'error', message: 'Script nicht gefunden' });
    }
    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    res.sendFile(scriptPath);
});

const downloadSetup = asyncHandler(async (req, res) => {
    // Agent-Key Auth für auto-update vom Agent selbst
    const agentKey = req.headers['x-agent-key'];
    const isAgent = validateAgentKey(agentKey, req.query.hostname);
    const isAdmin = req.user?.role_name === 'admin' || req.user?.role_name === 'super_admin';
    if (!isAgent && !isAdmin) {
        return res.status(401).json({ status: 'error', message: 'Unauthorized' });
    }

    const path = require('path');
    const fs = require('fs');

    // Zielversion: Gruppen-spezifisch (staged rollout) oder globaler Default — gleiche Logik wie checkin()
    let version = process.env.AGENT_VERSION || FALLBACK_AGENT_VERSION;
    const hostname = req.query.hostname;
    if (hostname) {
        try {
            const db = getDatabase();
            const agent = db.prepare('SELECT id, agent_version FROM monitoring_agents WHERE hostname = ?').get(hostname);
            const groupRow = agent && db.prepare(`
                SELECT pg.target_agent_version
                FROM patch_agent_groups pag
                JOIN patch_groups pg ON pg.id = pag.group_id
                WHERE pag.agent_id = ?
                ORDER BY pg.sort_order ASC
                LIMIT 1
            `).get(agent.id);
            if (groupRow?.target_agent_version) version = groupRow.target_agent_version;
            version = clampToNotOlder(version, agent?.agent_version);
        } catch { /* kein Gruppe zugewiesen → global */ }
    }

    const setupPath = path.join(__dirname, `../../agent/IT-Nexus-Agent-Setup-v${version}.exe`);
    if (!fs.existsSync(setupPath)) {
        return res.status(404).json({ status: 'error', message: 'Setup nicht gefunden' });
    }
    res.setHeader('Content-Type', 'application/octet-stream');
    res.setHeader('X-Agent-Version', version);
    res.setHeader('Content-Disposition', `attachment; filename="IT-Nexus-Agent-Setup-v${version}.exe"`);
    res.sendFile(setupPath);
});

module.exports = { checkin, announcementsPoll, getAll, getStatistics, getById, deleteAgent, downloadScript, downloadAnnWatcher, downloadSetup, enroll };
