/**
 * Room Status Manager
 * 
 * Manages an independent PostgreSQL connection pool for
 * the room_status.room_status_moment snapshot table.
 * Provides batch upsert with JSONB merge and auto-partition creation.
 */
import pg from 'pg';
import { randomUUID } from 'crypto';
import { logger } from '../utils/logger.js';

const { Pool } = pg;

export class RoomStatusManager {
    /**
     * @param {Object} dbConfig - roomStatusDb config from config.js
     */
    constructor(dbConfig) {
        this.pool = new Pool({
            host: dbConfig.host,
            port: dbConfig.port,
            user: dbConfig.user,
            password: dbConfig.password,
            database: dbConfig.database,
            max: dbConfig.max,
            ssl: dbConfig.ssl
        });
        this.schema = dbConfig.schema;
        this.table = dbConfig.table;
        this.fullTableName = `${this.schema}.${this.table}`;
        // Track which partitions we have already ensured
        this.knownPartitions = new Set();
    }

    /**
     * Batch upsert status rows into room_status_moment.
     * Uses ON CONFLICT for atomic merge.
     * 
     * @param {Array<Object>} rows - Array of merged status objects
     *   Each: { hotel_id, room_id, device_id, ts_ms, sys_lock_status, dev_loops, faulty_device_count }
     */
    async upsertBatch(rows) {
        if (!rows || rows.length === 0) return;

        // Pre-ensure all needed partitions exist before attempting upsert
        const newHotelIds = [...new Set(rows.map(r => r.hotel_id))]
            .filter(id => !this.knownPartitions.has(id));

        if (newHotelIds.length > 0) {
            await this._ensurePartitionsBatch(newHotelIds);
        }

        await this._doUpsert(rows);
    }

    /**
     * Execute the actual upsert SQL for a batch of rows.
     */
    async _doUpsert(rows) {
        const values = [];
        const placeholders = [];

        for (let i = 0; i < rows.length; i++) {
            const row = rows[i];
            const offset = i * 8; // Changed from 9 to 8
            values.push(
                row.guid || randomUUID(),          // $1
                row.ts_ms,                         // $2  
                row.hotel_id,                      // $3
                row.room_id,                       // $4
                row.device_id,                     // $5
                row.sys_lock_status,               // $6
                row.dev_loops ? JSON.stringify(row.dev_loops) : null,              // $7 (was $8)
                row.faulty_device_count ? JSON.stringify(row.faulty_device_count) : null  // $8 (was $9)
            );
            const p = (n) => `$${offset + n}`;
            placeholders.push(`(${p(1)}::uuid, ${p(2)}, ${p(3)}, ${p(4)}, ${p(5)}, ${p(6)}, ${p(7)}::jsonb, ${p(8)}::jsonb)`);
        }

        const sql = `
      INSERT INTO ${this.fullTableName} (
        guid, ts_ms, hotel_id, room_id, device_id,
        sys_lock_status, dev_loops, faulty_device_count
      ) VALUES ${placeholders.join(', ')}
      ON CONFLICT (hotel_id, room_id, device_id)
      DO UPDATE SET
        ts_ms = GREATEST(${this.fullTableName}.ts_ms, EXCLUDED.ts_ms),
        sys_lock_status = COALESCE(EXCLUDED.sys_lock_status, ${this.fullTableName}.sys_lock_status),
        dev_loops = CASE
          WHEN EXCLUDED.dev_loops IS NULL THEN ${this.fullTableName}.dev_loops
          ELSE COALESCE(${this.fullTableName}.dev_loops, '{}'::jsonb) || EXCLUDED.dev_loops
        END,
        faulty_device_count = COALESCE(EXCLUDED.faulty_device_count, ${this.fullTableName}.faulty_device_count)
    `;

        await this.pool.query(sql, values);
    }

    /**
     * Check if an error is a missing partition error.
     */
    _isPartitionMissingError(error) {
        const msg = error?.message || '';
        return msg.includes('no partition') || msg.includes('routing') ||
            (error?.code === '23514' && msg.includes('partition'));
    }

    /**
     * Batch-create LIST partitions for multiple hotel_ids in a single connection.
     * Uses CREATE TABLE IF NOT EXISTS (idempotent) — no check query needed.
     */
    async _ensurePartitionsBatch(hotelIds) {
        const client = await this.pool.connect();
        try {
            for (const hotelId of hotelIds) {
                const partitionName = `${this.schema}.${this.table}_h${hotelId}`;
                try {
                    await client.query(
                        `CREATE TABLE IF NOT EXISTS ${partitionName} PARTITION OF ${this.fullTableName} FOR VALUES IN (${hotelId})`
                    );
                    this.knownPartitions.add(hotelId);
                } catch (err) {
                    // Partition may already exist (race condition) — safe to ignore
                    if (!err.message?.includes('already exists')) {
                        logger.error('Error creating partition', { error: err?.message, hotelId });
                    }
                    this.knownPartitions.add(hotelId);
                }
            }
            if (hotelIds.length > 0) {
                logger.info(`Ensured ${hotelIds.length} room_status partitions`);
            }
        } finally {
            client.release();
        }
    }

    /**
     * Ensure a LIST partition exists for the given hotel_id (single).
     */
    async ensurePartition(hotelId) {
        if (this.knownPartitions.has(hotelId)) return;
        await this._ensurePartitionsBatch([hotelId]);
    }

    async testConnection() {
        try {
            await this.pool.query('SELECT 1');
            return true;
        } catch {
            return false;
        }
    }

    async close() {
        await this.pool.end();
    }
}
