#!/usr/bin/env node
// backfill_anomaly_log.js — One-time backfill of ivanti_sync_anomaly_log
//
// Synthesizes anomaly log entries from existing ivanti_archive_transitions
// and ivanti_counts_history data so the archive activity sparkline on the
// Findings Trend chart has historical data to display.
//
// Safe to run multiple times — checks for existing rows before inserting.
//
// Usage: node backend/migrations/backfill_anomaly_log.js

const path    = require('path');
const sqlite3 = require('sqlite3').verbose();

const DB_PATH = path.join(__dirname, '..', 'cve_database.db');

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

function dbGet(db, sql, params = []) {
    return new Promise((resolve, reject) => {
        db.get(sql, params, (err, row) => {
            if (err) reject(err);
            else resolve(row);
        });
    });
}

function dbRun(db, sql, params = []) {
    return new Promise((resolve, reject) => {
        db.run(sql, params, function (err) {
            if (err) reject(err);
            else resolve(this);
        });
    });
}

async function main() {
    const db = new sqlite3.Database(DB_PATH);

    // Check if anomaly log already has data
    const existing = await dbGet(db, 'SELECT COUNT(*) as cnt FROM ivanti_sync_anomaly_log');
    if (existing.cnt > 0) {
        console.log(`ivanti_sync_anomaly_log already has ${existing.cnt} rows — skipping backfill.`);
        console.log('To force re-run, delete existing rows first:');
        console.log('  sqlite3 backend/cve_database.db "DELETE FROM ivanti_sync_anomaly_log;"');
        db.close();
        return;
    }

    // Get archive transitions grouped by date
    const transitions = await dbAll(db,
        `SELECT DATE(transitioned_at) as date,
                to_state,
                reason,
                COUNT(*) as cnt
         FROM ivanti_archive_transitions
         GROUP BY date, to_state, reason
         ORDER BY date`
    );

    // Get counts history (last snapshot per day) for delta computation
    const countsRows = await dbAll(db,
        `SELECT date, open_count, closed_count FROM (
             SELECT DATE(recorded_at) AS date,
                    open_count, closed_count,
                    ROW_NUMBER() OVER (
                        PARTITION BY DATE(recorded_at)
                        ORDER BY recorded_at DESC
                    ) AS rn
             FROM ivanti_counts_history
         ) WHERE rn = 1
         ORDER BY date ASC`
    );

    // Build a map of date -> { open_count, closed_count }
    const countsMap = {};
    for (const row of countsRows) {
        countsMap[row.date] = { open: row.open_count, closed: row.closed_count };
    }

    // Build per-date anomaly summaries from transitions
    const dateMap = {};
    for (const t of transitions) {
        if (!dateMap[t.date]) {
            dateMap[t.date] = { archived: 0, returned: 0, classification: {} };
        }
        const entry = dateMap[t.date];

        if (t.to_state === 'ARCHIVED') {
            entry.archived += t.cnt;
            // All pre-feature transitions have reason 'severity_score_drift'
            // but from the investigation we know the 04/24 batch was mostly
            // BU reassignment. We can't retroactively classify without the
            // Ivanti API, so we label them as 'unclassified' (pre-feature).
            entry.classification.unclassified = (entry.classification.unclassified || 0) + t.cnt;
        } else if (t.to_state === 'RETURNED') {
            entry.returned += t.cnt;
        }
        // CLOSED transitions are not archive events — they're findings
        // confirmed in the closed set, so we don't count them as archived.
    }

    // Compute deltas and insert rows
    const dates = Object.keys(dateMap).sort();
    let inserted = 0;

    for (const date of dates) {
        const entry = dateMap[date];
        const counts = countsMap[date];

        // Find the previous day's counts for delta computation
        const dateIdx = countsRows.findIndex(r => r.date === date);
        let openDelta = 0;
        let closedDelta = 0;

        if (counts && dateIdx > 0) {
            const prev = countsRows[dateIdx - 1];
            openDelta = counts.open - prev.open_count;
            closedDelta = counts.closed - prev.closed_count;
        }

        const isSignificant = entry.archived > 5 ? 1 : 0;
        const classificationJson = JSON.stringify(entry.classification);

        await dbRun(db,
            `INSERT INTO ivanti_sync_anomaly_log
             (sync_timestamp, open_count_delta, closed_count_delta,
              newly_archived_count, returned_count, classification_json, is_significant)
             VALUES (?, ?, ?, ?, ?, ?, ?)`,
            [
                `${date}T23:59:00`,
                openDelta,
                closedDelta,
                entry.archived,
                entry.returned,
                classificationJson,
                isSignificant,
            ]
        );
        inserted++;

        const sigLabel = isSignificant ? ' [SIGNIFICANT]' : '';
        console.log(`  ${date}: ${entry.archived} archived, ${entry.returned} returned, delta open=${openDelta} closed=${closedDelta}${sigLabel}`);
    }

    console.log(`\nBackfill complete: ${inserted} anomaly log entries created.`);
    db.close();
}

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