"use strict";
/**
 * AccessReview -- Quarterly access review for compliance frameworks.
 *
 * SOC 2, HIPAA, and ISO 27001 require periodic review of access rights.
 * This module automates the review process:
 *   - Generates access review reports
 *   - Flags inactive operators for deactivation
 *   - Tracks review completion status
 *   - Maintains review audit trail
 *
 * T-060: Quarterly access review automation.
 *
 * @connexum/ai-governance
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessReview = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto = __importStar(require("crypto"));
const log_1 = require("../util/log");
const log = (0, log_1.createLogger)('access-review');
class AccessReview {
    config;
    cyclesFile;
    auditFile;
    constructor(config) {
        this.config = {
            reviewFrequencyMs: 30 * 60 * 1000, // 30 min (DEV MODE -- production: 90 * 24 * 60 * 60 * 1000 / 90 days)
            inactivityThresholdDays: 1, // 1 day (DEV MODE -- production: 30)
            organizationName: 'Organization',
            ...config,
        };
        if (!fs.existsSync(config.reviewDir)) {
            fs.mkdirSync(config.reviewDir, { recursive: true });
        }
        this.cyclesFile = path.join(config.reviewDir, 'review-cycles.json');
        this.auditFile = path.join(config.reviewDir, 'review-audit.jsonl');
    }
    /**
     * Start a new review cycle.
     */
    startReview(operators) {
        const now = new Date();
        const cycle = {
            id: `review_${crypto.randomBytes(6).toString('hex')}`,
            startedAt: now.toISOString(),
            deadline: new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000).toISOString(), // 14 days to complete
            status: 'IN_PROGRESS',
            entries: operators.map(op => this.createEntry(op)),
        };
        const cycles = this.loadCycles();
        cycles.push(cycle);
        this.saveCycles(cycles);
        this.audit('REVIEW_STARTED', cycle.id, { operatorCount: operators.length });
        log.info('Access review started', { id: cycle.id, operators: operators.length });
        return cycle;
    }
    /**
     * Record a review decision for an operator.
     */
    recordDecision(cycleId, operatorId, decision, reviewedBy, notes) {
        const cycles = this.loadCycles();
        const cycle = cycles.find(c => c.id === cycleId);
        if (!cycle)
            throw new Error(`Review cycle ${cycleId} not found`);
        if (cycle.status === 'COMPLETED')
            throw new Error('Review cycle already completed');
        const entry = cycle.entries.find(e => e.operatorId === operatorId);
        if (!entry)
            throw new Error(`Operator ${operatorId} not in this review`);
        entry.decision = decision;
        entry.reviewedBy = reviewedBy;
        entry.reviewedAt = new Date().toISOString();
        entry.notes = notes;
        this.saveCycles(cycles);
        this.audit('DECISION_RECORDED', cycleId, { operatorId, decision, reviewedBy });
    }
    /**
     * Complete a review cycle (all decisions must be recorded).
     */
    completeReview(cycleId) {
        const cycles = this.loadCycles();
        const cycle = cycles.find(c => c.id === cycleId);
        if (!cycle)
            throw new Error(`Review cycle ${cycleId} not found`);
        const pendingCount = cycle.entries.filter(e => e.decision === 'PENDING').length;
        if (pendingCount > 0) {
            throw new Error(`Cannot complete: ${pendingCount} operators still pending review`);
        }
        cycle.status = 'COMPLETED';
        cycle.completedAt = new Date().toISOString();
        cycle.summary = this.computeSummary(cycle);
        this.saveCycles(cycles);
        this.audit('REVIEW_COMPLETED', cycleId, cycle.summary);
        return cycle.summary;
    }
    /**
     * Check if a review is due based on the last completed review.
     *
     * SYSTEMIC-M2 contract (2026-04-22): the return direction is
     * "review IS due" -- callers must treat `true` as "access must be
     * re-attested NOW" and `false` as "review not yet due, no action
     * needed." Any caller that interprets `true` as "no review needed"
     * has the direction inverted and must be fixed immediately.
     *
     * @returns {boolean} true = review is due (access must be re-attested);
     *                    false = review not yet due, no action needed.
     */
    isReviewDue() {
        const cycles = this.loadCycles();
        const completed = cycles
            .filter(c => c.status === 'COMPLETED' && c.completedAt)
            .sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime());
        if (completed.length === 0)
            return true;
        const lastCompleted = new Date(completed[0].completedAt).getTime();
        return (Date.now() - lastCompleted) > this.config.reviewFrequencyMs;
    }
    /**
     * Get the current (most recent) review cycle.
     */
    getCurrentCycle() {
        const cycles = this.loadCycles();
        return cycles[cycles.length - 1];
    }
    /**
     * Get all review cycles.
     */
    getAllCycles() {
        return this.loadCycles();
    }
    /**
     * Get overdue review cycles.
     */
    getOverdue() {
        const now = Date.now();
        const cycles = this.loadCycles();
        return cycles.filter(c => {
            if (c.status === 'COMPLETED')
                return false;
            return new Date(c.deadline).getTime() < now;
        });
    }
    /**
     * Get review statistics across all cycles.
     */
    getStats() {
        const cycles = this.loadCycles();
        const completed = cycles.filter(c => c.status === 'COMPLETED');
        const overdue = this.getOverdue();
        const lastCompleted = completed
            .sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
        return {
            totalCycles: cycles.length,
            completedCycles: completed.length,
            overdueCycles: overdue.length,
            isReviewDue: this.isReviewDue(),
            lastCompletedAt: lastCompleted?.completedAt,
        };
    }
    // --- Private ---
    createEntry(op) {
        const flags = [];
        const now = Date.now();
        const threshold = this.config.inactivityThresholdDays * 24 * 60 * 60 * 1000;
        if (!op.active) {
            flags.push('INACTIVE');
        }
        if (op.lastActivity) {
            const lastActive = new Date(op.lastActivity).getTime();
            if (now - lastActive > threshold) {
                flags.push('NO_RECENT_LOGIN');
            }
        }
        else {
            flags.push('NO_RECENT_LOGIN');
        }
        if (op.role === 'ADMIN') {
            flags.push('ELEVATED_PRIVILEGES');
        }
        if (op.createdAt) {
            const created = new Date(op.createdAt).getTime();
            if (now - created < 30 * 24 * 60 * 60 * 1000) {
                flags.push('NEW_OPERATOR');
            }
        }
        return {
            operatorId: op.id,
            operatorName: op.name,
            role: op.role,
            active: op.active,
            lastActivity: op.lastActivity,
            flags,
            decision: 'PENDING',
        };
    }
    computeSummary(cycle) {
        const entries = cycle.entries;
        return {
            totalOperators: entries.length,
            retained: entries.filter(e => e.decision === 'RETAIN').length,
            modified: entries.filter(e => e.decision === 'MODIFY').length,
            revoked: entries.filter(e => e.decision === 'REVOKE').length,
            pending: entries.filter(e => e.decision === 'PENDING').length,
            flaggedInactive: entries.filter(e => e.flags.includes('INACTIVE') || e.flags.includes('NO_RECENT_LOGIN')).length,
            completionRate: entries.filter(e => e.decision !== 'PENDING').length / entries.length,
        };
    }
    loadCycles() {
        if (!fs.existsSync(this.cyclesFile))
            return [];
        try {
            return JSON.parse(fs.readFileSync(this.cyclesFile, 'utf-8'));
        }
        catch {
            return [];
        }
    }
    saveCycles(cycles) {
        fs.writeFileSync(this.cyclesFile, JSON.stringify(cycles, null, 2));
    }
    audit(action, cycleId, data) {
        const entry = {
            timestamp: new Date().toISOString(),
            action,
            cycleId,
            ...data,
        };
        try {
            fs.appendFileSync(this.auditFile, JSON.stringify(entry) + '\n');
        }
        catch {
            log.error('Failed to write review audit', { cycleId, action });
        }
    }
}
exports.AccessReview = AccessReview;
//# sourceMappingURL=access-review.js.map