/**
 * Status Batch Processor
 * 
 * Collects status updates for room_status_moment table,
 * merges updates for the same device within a batch window,
 * and flushes them via RoomStatusManager.
 * 
 * Errors during flush are logged but never thrown, to protect the main pipeline.
 */
import { logger } from '../utils/logger.js';

export class StatusBatchProcessor {
    /**
     * @param {import('./roomStatusManager.js').RoomStatusManager} roomStatusManager
     * @param {Object} options
     * @param {number} [options.flushInterval=500] - Flush interval in ms
     * @param {number} [options.maxBufferSize=200] - Max items before forced flush
     * @param {import('../redis/redisIntegration.js').RedisIntegration} [options.redisIntegration] - For error reporting
     */
    constructor(roomStatusManager, options = {}) {
        this.roomStatusManager = roomStatusManager;
        this.flushInterval = options.flushInterval || 500;
        this.maxBufferSize = options.maxBufferSize || 200;
        this.redisIntegration = options.redisIntegration || null;

        /** @type {Map<string, Object>} compositeKey -> mergedState */
        this.buffer = new Map();
        this.timer = null;
    }

    /**
     * Build composite key for deduplication.
     */
    _key(update) {
        return `${update.hotel_id}:${update.room_id}:${update.device_id}`;
    }

    /**
     * Add a status update to the buffer, merging with any existing entry for the same device.
     * @param {Object} update - Output from extractStatusUpdate()
     */
    add(update) {
        if (!update) return;

        const key = this._key(update);
        const existing = this.buffer.get(key);

        if (existing) {
            // Merge: take latest ts_ms
            existing.ts_ms = Math.max(existing.ts_ms, update.ts_ms);

            // sys_lock_status: prefer newer non-null value
            if (update.sys_lock_status != null) {
                existing.sys_lock_status = update.sys_lock_status;
            }

            // dev_loops: merge keys (new overwrites old for same key)
            if (update.dev_loops) {
                existing.dev_loops = existing.dev_loops
                    ? { ...existing.dev_loops, ...update.dev_loops }
                    : update.dev_loops;
            }

            // faulty_device_count: full replacement (newer wins)
            if (update.faulty_device_count) {
                existing.faulty_device_count = update.faulty_device_count;
            }
        } else {
            // Clone to avoid mutation of caller's object
            this.buffer.set(key, { ...update });
        }

        // Check if we should flush
        if (this.buffer.size >= this.maxBufferSize && !this.isFlushing) {
            this.flush();
        } else if (!this.timer && !this.isFlushing) {
            this.timer = setTimeout(() => this.flush(), this.flushInterval);
        }
    }

    /**
     * Flush all buffered status updates to the database.
     * Errors are caught and logged, never thrown.
     */
    async flush() {
        if (this.timer) {
            clearTimeout(this.timer);
            this.timer = null;
        }

        if (this.buffer.size === 0) return;
        if (this.isFlushing) return;
        this.isFlushing = true;

        // Snapshot and clear the buffer atomically
        const rows = [...this.buffer.values()];
        this.buffer.clear();

        try {
            logger.info('StatusBatchProcessor flushing rows', { count: rows.length, sampleRowKeys: rows.map(r => r.device_id).slice(0, 5) });
            await this.roomStatusManager.upsertBatch(rows);
            logger.info('StatusBatchProcessor flushed successfully', { count: rows.length });
        } catch (error) {
            logger.error('StatusBatchProcessor flush failed', {
                error: error?.message,
                stack: error?.stack,
                count: rows.length
            });

            // Report to Redis console if available
            if (this.redisIntegration) {
                try {
                    await this.redisIntegration.error('StatusBatchProcessor flush failed', {
                        module: 'room_status',
                        count: rows.length,
                        stack: error?.message
                    });
                } catch {
                    // Silently ignore Redis reporting errors
                }
            }
            // IMPORTANT: Do NOT re-throw. This protects the main Kafka pipeline.
        } finally {
            this.isFlushing = false;
            // Catch up if buffer refilled wildly during flush
            if (this.buffer.size >= this.maxBufferSize) {
                this.flush();
            } else if (this.buffer.size > 0 && !this.timer) {
                this.timer = setTimeout(() => this.flush(), this.flushInterval);
            }
        }
    }
}
