import { logger } from '../utils/logger.js';
import dbManager from './databaseManager.js';

const PARENT_TABLE = 'rcu_action.rcu_action_events';
const PARTITION_TABLESPACE = 'ts_hot';
const PARTITION_SCHEMA = 'rcu_action';
const PARTITION_PREFIX = 'rcu_action_events_';
const PARENT_INDEX_STATEMENTS = [
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_hotel_id ON rcu_action.rcu_action_events (hotel_id);',
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_room_id ON rcu_action.rcu_action_events (room_id);',
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_device_id ON rcu_action.rcu_action_events (device_id);',
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_direction ON rcu_action.rcu_action_events (direction);',
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_cmd_word ON rcu_action.rcu_action_events (cmd_word);',
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_action_type ON rcu_action.rcu_action_events (action_type);',
  'CREATE INDEX IF NOT EXISTS idx_rcu_action_query_main ON rcu_action.rcu_action_events (hotel_id, room_id, ts_ms DESC);'
];

class PartitionManager {
  toSqlTextLiteral(value) {
    return `'${String(value).replace(/'/g, "''")}'`;
  }

  buildForceTablespaceSql({ schema, partition, tablespace }) {
    return `
      DO $$
      DECLARE
        v_schema text := ${this.toSqlTextLiteral(schema)};
        v_partition text := ${this.toSqlTextLiteral(partition)};
        v_hot text := ${this.toSqlTextLiteral(tablespace)};
        v_part_oid oid;
        v_toast_oid oid;
        r record;
      BEGIN
        SELECT c.oid INTO v_part_oid
        FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE n.nspname = v_schema AND c.relname = v_partition AND c.relkind = 'r';

        IF v_part_oid IS NULL THEN
          RAISE EXCEPTION 'partition %.% not found', v_schema, v_partition;
        END IF;

        -- 1) 分区表对象 -> hot
        EXECUTE format('ALTER TABLE %I.%I SET TABLESPACE %I', v_schema, v_partition, v_hot);

        -- 2) 分区全部索引 -> hot
        FOR r IN
          SELECT idxn.nspname AS index_schema, i.relname AS index_name
          FROM pg_index x
          JOIN pg_class t ON t.oid = x.indrelid
          JOIN pg_namespace nt ON nt.oid = t.relnamespace
          JOIN pg_class i ON i.oid = x.indexrelid
          JOIN pg_namespace idxn ON idxn.oid = i.relnamespace
          LEFT JOIN pg_tablespace ts ON ts.oid = i.reltablespace
          WHERE nt.nspname = v_schema
            AND t.relname = v_partition
            AND COALESCE(ts.spcname, 'pg_default') <> v_hot
        LOOP
          EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, v_hot);
        END LOOP;

        -- 3) TOAST 表 + TOAST 全部索引 -> hot（若存在）
        SELECT reltoastrelid INTO v_toast_oid FROM pg_class WHERE oid = v_part_oid;
        IF v_toast_oid IS NOT NULL AND v_toast_oid <> 0 THEN
          EXECUTE format('ALTER TABLE %s SET TABLESPACE %I', v_toast_oid::regclass, v_hot);

          FOR r IN
            SELECT idxn.nspname AS index_schema, i.relname AS index_name
            FROM pg_index x
            JOIN pg_class i ON i.oid = x.indexrelid
            JOIN pg_namespace idxn ON idxn.oid = i.relnamespace
            LEFT JOIN pg_tablespace ts ON ts.oid = i.reltablespace
            WHERE x.indrelid = v_toast_oid
              AND COALESCE(ts.spcname, 'pg_default') <> v_hot
          LOOP
            EXECUTE format('ALTER INDEX %I.%I SET TABLESPACE %I', r.index_schema, r.index_name, v_hot);
          END LOOP;
        END IF;

        -- 4) 统计信息
        EXECUTE format('ANALYZE %I.%I', v_schema, v_partition);
      END $$;
    `;
  }

  async ensureParentIndexes(client) {
    for (const sql of PARENT_INDEX_STATEMENTS) {
      await client.query(sql);
    }
  }

  /**
   * Calculate the start and end timestamps (milliseconds) for a given date.
   * @param {Date} date - The date to calculate for.
   * @returns {Object} { startMs, endMs, partitionSuffix }
   */
  getPartitionInfo(date) {
    const yyyy = date.getFullYear();
    const mm = String(date.getMonth() + 1).padStart(2, '0');
    const dd = String(date.getDate()).padStart(2, '0');
    const partitionSuffix = `${yyyy}${mm}${dd}`;

    const start = new Date(date);
    start.setHours(0, 0, 0, 0);
    const startMs = start.getTime();

    const end = new Date(date);
    end.setDate(end.getDate() + 1);
    end.setHours(0, 0, 0, 0);
    const endMs = end.getTime();

    return { startMs, endMs, partitionSuffix };
  }

  /**
   * Ensure partitions exist for the next N days.
   * @param {number} daysAhead - Number of days to pre-create.
   */
  async ensurePartitions(daysAhead = 30) {
    const client = await dbManager.pool.connect();
    try {
      logger.info(`Starting partition check for the next ${daysAhead} days...`);
      await this.ensureParentIndexes(client);
      const now = new Date();

      for (let i = 0; i < daysAhead; i++) {
        const targetDate = new Date(now);
        targetDate.setDate(now.getDate() + i);

        const { startMs, endMs, partitionSuffix } = this.getPartitionInfo(targetDate);
        const partitionTable = `${PARTITION_PREFIX}${partitionSuffix}`;
        const partitionName = `${PARTITION_SCHEMA}.${partitionTable}`;

        // Check if partition exists
        const checkSql = `
          SELECT to_regclass($1) as exists;
        `;
        const checkRes = await client.query(checkSql, [partitionName]);
        
        if (!checkRes.rows[0].exists) {
          logger.info(`Creating partition ${partitionName} for range [${startMs}, ${endMs})`);
          const createSql = `
            CREATE TABLE IF NOT EXISTS ${partitionName}
            PARTITION OF ${PARENT_TABLE}
            FOR VALUES FROM (${startMs}) TO (${endMs})
            TABLESPACE ${PARTITION_TABLESPACE};
          `;
          await client.query(createSql);

          const forceTablespaceSql = this.buildForceTablespaceSql({
            schema: PARTITION_SCHEMA,
            partition: partitionTable,
            tablespace: PARTITION_TABLESPACE
          });
          await client.query(forceTablespaceSql);
        }
      }
      logger.info('Partition check completed.');
    } catch (err) {
      logger.error('Error ensuring partitions:', err);
      throw err;
    } finally {
      client.release();
    }
  }
}

export default new PartitionManager();
