import { ErrorFactory, Logger } from '../../../src/index.js';
import { WorkerCreationStatus, WorkerFactory } from './WorkerFactory.js';
const DEFAULT_CONFIG = {
    enabled: true,
    tickIntervalMs: 1000,
    concurrencyLimit: 50,
    checkTimeoutMs: 5000,
    intervalHealthyMs: 30000,
    intervalSuspectMs: 5000,
    failureThreshold: 2,
    historyLimit: 50,
};
// Module-level state (Singleton by nature of ESM)
const registry = new Map();
let config = { ...DEFAULT_CONFIG };
let timer = null;
let runningChecks = 0;
// Internal Helpers
const persistStatusChange = async (name, status, lastError) => {
    try {
        await WorkerFactory.updateStatus(name, status, lastError);
    }
    catch (err) {
        Logger.error(`Failed to persist status change for ${name}`, err);
    }
};
const verifyWorkerHealth = async (worker) => {
    // Check if isClosing exists (isClosing check safe for mocks)
    const workerAny = worker;
    const isClosingFn = workerAny['isClosing'];
    if (worker.isPaused() ||
        (typeof isClosingFn === 'function' && isClosingFn())) {
        return false;
    }
    const isRunning = await worker.isRunning();
    if (!isRunning)
        return false;
    const client = await worker.client;
    const pingResult = await client.ping();
    if (pingResult !== 'PONG') {
        throw ErrorFactory.createWorkerError(`Redis ping failed: ${pingResult}`);
    }
    return true;
};
const withTimeout = async (promise, timeoutMs, onTimeout) => {
    let timeoutId = null;
    return await new Promise((resolve, reject) => {
        timeoutId = globalThis.setTimeout(() => {
            if (timeoutId) {
                clearTimeout(timeoutId);
                timeoutId = null;
            }
            reject(onTimeout());
        }, timeoutMs);
        timeoutId.unref();
        promise
            .then(resolve)
            .catch(reject)
            .finally(() => {
            if (timeoutId) {
                clearTimeout(timeoutId);
                timeoutId = null;
            }
        });
    });
};
const updateState = (state, isHealthy, errorMsg, latency) => {
    const now = new Date();
    state.lastCheck = now;
    // Determine status (healthy > degraded > critical)
    let status;
    if (isHealthy) {
        status = 'healthy';
    }
    else if (state.consecutiveFailures < config.failureThreshold) {
        status = 'degraded';
    }
    else {
        status = 'critical';
    }
    // Create Check Result
    const result = {
        timestamp: now,
        status,
        latency,
        message: errorMsg,
    };
    // Add to history
    state.history.push(result);
    if (state.history.length > config.historyLimit) {
        state.history.shift();
    }
    // Callbacks
    if (!isHealthy && state.config?.degradedCallback) {
        state.config.degradedCallback(state.name, result);
    }
    if (result.status === 'critical' && state.config?.criticalCallback) {
        state.config.criticalCallback(state.name, result);
    }
    if (isHealthy) {
        state.consecutiveFailures = 0;
        if (state.status !== WorkerCreationStatus.RUNNING) {
            persistStatusChange(state.name, WorkerCreationStatus.RUNNING);
            state.status = WorkerCreationStatus.RUNNING;
            Logger.info(`Worker ${state.name} recovered to RUNNING`);
        }
        const jitter = Math.floor(Math.random() * 500); //NOSONAR
        state.nextCheck = new Date(now.getTime() + config.intervalHealthyMs + jitter);
    }
    else {
        state.consecutiveFailures++;
        if (state.consecutiveFailures >= config.failureThreshold &&
            state.status !== WorkerCreationStatus.FAILED) {
            persistStatusChange(state.name, WorkerCreationStatus.FAILED, errorMsg);
            state.status = WorkerCreationStatus.FAILED;
            Logger.warn(`Worker ${state.name} marked FAILED after ${state.consecutiveFailures} checks`, {
                error: errorMsg,
            });
        }
        const jitter = Math.floor(Math.random() * 500); //NOSONAR
        state.nextCheck = new Date(now.getTime() + config.intervalSuspectMs + jitter);
    }
};
const performCheck = async (state) => {
    const startTime = Date.now();
    let isHealthy = false;
    let errorMsg;
    try {
        if (!state.worker) {
            throw ErrorFactory.createWorkerError('Worker instance not available');
        }
        isHealthy = await withTimeout(verifyWorkerHealth(state.worker), config.checkTimeoutMs, () => ErrorFactory.createWorkerError('Health check timeout'));
    }
    catch (err) {
        isHealthy = false;
        errorMsg = err.message;
    }
    const duration = Date.now() - startTime;
    updateState(state, isHealthy, errorMsg, duration);
};
const scheduleCheck = async (state) => {
    state.inProgress = true;
    runningChecks++;
    performCheck(state).finally(() => {
        state.inProgress = false;
        runningChecks--;
    });
};
const tick = async () => {
    const now = new Date();
    const candidates = [];
    for (const state of registry.values()) {
        if (runningChecks >= config.concurrencyLimit)
            break;
        // Skip if checks are paused or if worker instance is missing (wait for register)
        if (!state.worker && !state.queueName)
            continue;
        if (!state.inProgress && state.nextCheck <= now) {
            candidates.push(state);
        }
    }
    for (const candidate of candidates) {
        if (runningChecks >= config.concurrencyLimit)
            break;
        scheduleCheck(candidate);
    }
};
const start = () => {
    if (timer)
        return;
    timer = setInterval(() => tick(), config.tickIntervalMs);
    Logger.debug('HealthMonitor started');
};
const stop = () => {
    if (timer) {
        clearInterval(timer);
        timer = null;
    }
    Logger.debug('HealthMonitor stopped');
};
// Exported Public Methods
const configure = (newConfig) => {
    config = { ...config, ...newConfig };
    if (timer) {
        stop();
        start();
    }
};
const register = (name, worker, queueName) => {
    let state = registry.get(name);
    if (state) {
        // update existing entry (maybe created by startMonitoring)
        state.worker = worker;
        state.queueName = queueName;
    }
    else {
        // Add jitter
        const initialDelay = Math.floor(Math.random() * 5000); //NOSONAR
        state = {
            name,
            worker,
            queueName,
            status: WorkerCreationStatus.STARTING,
            lastCheck: new Date(),
            nextCheck: new Date(Date.now() + initialDelay),
            consecutiveFailures: 0,
            inProgress: false,
            history: [],
        };
        registry.set(name, state);
    }
    if (!timer) {
        start();
    }
};
const startMonitoring = (name, monitorConfig) => {
    const state = registry.get(name);
    if (state) {
        if (monitorConfig)
            state.config = { ...state.config, ...monitorConfig };
    }
    else {
        // Worker instance not yet registered, create placeholder
        const initialDelay = Math.floor(Math.random() * 5000); //NOSONAR
        registry.set(name, {
            name,
            status: WorkerCreationStatus.STARTING,
            lastCheck: new Date(),
            nextCheck: new Date(Date.now() + initialDelay),
            consecutiveFailures: 0,
            inProgress: false,
            history: [],
            config: monitorConfig,
        });
    }
    if (!timer)
        start();
};
const unregister = (name) => {
    registry.delete(name);
    if (registry.size === 0) {
        stop();
    }
};
const stopMonitoring = (name) => {
    unregister(name);
};
const updateConfig = (name, monitorConfig) => {
    startMonitoring(name, monitorConfig);
};
const getCurrentHealth = (name) => {
    const state = registry.get(name);
    if (!state || state.history.length === 0)
        return null;
    return state.history[state.history.length - 1];
};
const getHealthHistory = (name, limit) => {
    const state = registry.get(name);
    if (!state)
        return [];
    const history = state.history;
    return limit ? history.slice(-limit) : history;
};
const getHealthTrend = (name) => {
    const history = getHealthHistory(name, 10);
    const uptime = history.filter((h) => h.status === 'healthy').length / (history.length || 1);
    return { uptime, samples: history.length };
};
const getSummary = async () => {
    const summary = {
        total: registry.size,
        healthy: 0,
        degraded: 0,
        critical: 0,
        details: [],
    };
    for (const [name, state] of registry) {
        const lastResult = state.history[state.history.length - 1];
        const status = lastResult?.status || 'unknown';
        if (status === 'healthy')
            summary.healthy++;
        else if (status === 'degraded')
            summary.degraded++;
        else if (status === 'critical')
            summary.critical++;
        summary.details.push({
            name,
            status,
            lastCheck: state.lastCheck,
        });
    }
    return summary;
};
const shutdown = () => {
    stop();
    registry.clear();
};
export const HealthMonitor = Object.freeze({
    configure,
    register,
    unregister,
    start,
    stop,
    startMonitoring,
    stopMonitoring,
    updateConfig,
    getCurrentHealth,
    getHealthHistory,
    getHealthTrend,
    getSummary,
    shutdown,
});
