#!/usr/bin/env node
// export-reassigned-findings.js — Generate an xlsx with findings reassigned to SDIT-CSD-ITLS-PIES
//
// Pulls data from the archive database and the BU reassignment check results.
// Outputs to docs/reassigned-findings-2026-04-24.xlsx
//
// Usage: node backend/scripts/export-reassigned-findings.js

require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });

const path    = require('path');
const sqlite3 = require('sqlite3').verbose();
const XLSX    = require('xlsx');
const { ivantiPost } = require('../helpers/ivantiApi');

const DB_PATH     = path.join(__dirname, '..', 'cve_database.db');
const OUTPUT_PATH = path.join(__dirname, '..', '..', 'docs', 'reassigned-findings-2026-04-24.xlsx');

function dbAll(db, sql, params = []) {
    return new Promise((resolve, reject) => {
        db.all(sql, params, (err, rows) => {
            if (err) reject(err);
            else resolve(rows || []);
        });
    });
}

async function queryByFindingIds(findingIds, apiKey, clientId, skipTls) {
    const urlPath = `/client/${encodeURIComponent(clientId)}/hostFinding/search`;
    const results = new Map();
    const chunkSize = 50;

    for (let i = 0; i < findingIds.length; i += chunkSize) {
        const chunk = findingIds.slice(i, i + chunkSize);
        const idList = chunk.join(',');
        const filters = [{
            field: 'id', exclusive: false, operator: 'IN',
            orWithPrevious: false, implicitFilters: [],
            value: idList, caseSensitive: false
        }];

        let page = 0;
        let totalPages = 1;
        do {
            try {
                const body = { filters, projection: 'internal', sort: [{ field: 'severity', direction: 'ASC' }], page, size: 100 };
                const result = await ivantiPost(urlPath, body, apiKey, skipTls);
                if (result.status !== 200) break;
                const data = JSON.parse(result.body);
                totalPages = data.page?.totalPages || 1;
                const findings = data._embedded?.hostFindings || [];
                for (const f of findings) {
                    const bu = f.assetCustomAttributes?.['1550_host_1']?.[0] || 'UNKNOWN';
                    const wfDist = f.workflowDistribution || {};
                    const fpBuckets = [
                        ...(wfDist.approvedWorkflows || []), ...(wfDist.actionableWorkflows || []),
                        ...(wfDist.requestedWorkflows || []), ...(wfDist.reworkedWorkflows || []),
                        ...(wfDist.rejectedWorkflows || []), ...(wfDist.expiredWorkflows || []),
                    ].filter(w => (w.generatedId || '').startsWith('FP#'));
                    const fp = fpBuckets[0] || null;
                    results.set(String(f.id), {
                        bu,
                        severity: typeof f.severity === 'number' ? f.severity : parseFloat(f.severity) || 0,
                        state: f.status || '',
                        fpId: fp ? fp.generatedId : '',
                        fpState: fp ? fp.state : '',
                        hostName: f.host?.hostName || '',
                        ipAddress: f.host?.ipAddress || '',
                        title: f.title || '',
                    });
                }
                page++;
            } catch (err) {
                console.error(`  Error on batch at ${i}:`, err.message);
                break;
            }
        } while (page < totalPages);
    }
    return results;
}

async function main() {
    const apiKey   = process.env.IVANTI_API_KEY;
    const clientId = process.env.IVANTI_CLIENT_ID || '1550';
    const skipTls  = process.env.IVANTI_SKIP_TLS === 'true';

    const db = new sqlite3.Database(DB_PATH);

    // Get all archived/closed findings from the archive
    const archived = await dbAll(db,
        `SELECT finding_id, last_severity, finding_title, host_name, ip_address, current_state,
                DATE(first_archived_at) as archived_date, DATE(last_transition_at) as last_transition_date
         FROM ivanti_finding_archives
         WHERE current_state IN ('ARCHIVED', 'CLOSED')
         ORDER BY current_state, last_severity DESC`
    );

    const ids = archived.map(a => a.finding_id);
    console.log(`Querying Ivanti for ${ids.length} findings...`);
    const currentData = await queryByFindingIds(ids, apiKey, clientId, skipTls);

    // Build rows for each sheet
    const reassignedRows = [];
    const goneRows = [];
    const sameBuRows = [];

    for (const arch of archived) {
        const current = currentData.get(arch.finding_id);

        if (!current) {
            goneRows.push({
                'Finding ID': arch.finding_id,
                'Title': arch.finding_title,
                'Last Severity': arch.last_severity,
                'Host': arch.host_name,
                'IP Address': arch.ip_address,
                'Archive State': arch.current_state,
                'Archived Date': arch.archived_date,
                'Status': 'Gone from platform',
            });
        } else if (current.bu !== 'NTS-AEO-ACCESS-ENG' && current.bu !== 'NTS-AEO-STEAM') {
            reassignedRows.push({
                'Finding ID': arch.finding_id,
                'Title': current.title || arch.finding_title,
                'Last Severity (STEAM)': arch.last_severity,
                'Current Severity': current.severity,
                'Host': current.hostName || arch.host_name,
                'IP Address': current.ipAddress || arch.ip_address,
                'Previous BU': 'NTS-AEO-STEAM / ACCESS-ENG',
                'Current BU': current.bu,
                'Current State': current.state,
                'FP Workflow': current.fpId ? `${current.fpId} (${current.fpState})` : '',
                'Archive State': arch.current_state,
                'Archived Date': arch.archived_date,
            });
        } else {
            sameBuRows.push({
                'Finding ID': arch.finding_id,
                'Title': current.title || arch.finding_title,
                'Severity': current.severity,
                'Host': current.hostName || arch.host_name,
                'IP Address': current.ipAddress || arch.ip_address,
                'BU': current.bu,
                'Current State': current.state,
                'FP Workflow': current.fpId ? `${current.fpId} (${current.fpState})` : '',
                'Archive State': arch.current_state,
            });
        }
    }

    // Create workbook
    const wb = XLSX.utils.book_new();

    // Sheet 1: Reassigned findings
    const ws1 = XLSX.utils.json_to_sheet(reassignedRows);
    // Set column widths
    ws1['!cols'] = [
        { wch: 14 }, { wch: 55 }, { wch: 18 }, { wch: 16 },
        { wch: 30 }, { wch: 16 }, { wch: 28 }, { wch: 24 },
        { wch: 14 }, { wch: 24 }, { wch: 14 }, { wch: 14 },
    ];
    XLSX.utils.book_append_sheet(wb, ws1, 'Reassigned to SDIT-PIES');

    // Sheet 2: Gone from platform
    if (goneRows.length > 0) {
        const ws2 = XLSX.utils.json_to_sheet(goneRows);
        ws2['!cols'] = [
            { wch: 14 }, { wch: 55 }, { wch: 14 }, { wch: 30 },
            { wch: 16 }, { wch: 14 }, { wch: 14 }, { wch: 20 },
        ];
        XLSX.utils.book_append_sheet(wb, ws2, 'Gone from Platform');
    }

    // Sheet 3: Still same BU
    if (sameBuRows.length > 0) {
        const ws3 = XLSX.utils.json_to_sheet(sameBuRows);
        ws3['!cols'] = [
            { wch: 14 }, { wch: 55 }, { wch: 10 }, { wch: 30 },
            { wch: 16 }, { wch: 24 }, { wch: 14 }, { wch: 24 }, { wch: 14 },
        ];
        XLSX.utils.book_append_sheet(wb, ws3, 'Still Same BU');
    }

    // Write file
    XLSX.writeFile(wb, OUTPUT_PATH);
    console.log(`\nExported to: ${OUTPUT_PATH}`);
    console.log(`  Reassigned: ${reassignedRows.length}`);
    console.log(`  Gone:       ${goneRows.length}`);
    console.log(`  Same BU:    ${sameBuRows.length}`);

    db.close();
}

main().catch(err => {
    console.error('Fatal error:', err);
    process.exit(1);
});
