import { Pool } from "pg";
import { getEntityMetaConfig } from "../../types/meta.types.js";
import { parseSSLConfig } from "../db.js";
const databaseUrl = process.env.DATABASE_URL;
const pool = new Pool({
  connectionString: databaseUrl,
  ssl: parseSSLConfig(databaseUrl),
  max: 20,
  idleTimeoutMillis: 3e4,
  connectionTimeoutMillis: 1e4
});
class TransactionalError extends Error {
  constructor(message, code, originalError) {
    super(message);
    this.code = code;
    this.originalError = originalError;
    this.name = "TransactionalError";
  }
}
class TransactionalMetaService {
  static DEFAULT_OPTIONS = {
    lockTimeout: 5e3,
    retryOnDeadlock: true,
    maxRetries: 3,
    initialRetryDelay: 100
  };
  /**
   * Update metadata with row-level lock (SELECT FOR UPDATE)
   *
   * CRITICAL: updateFn MUST be synchronous to avoid holding locks during I/O operations
   *
   * @param entityType - Entity type (e.g., 'user', 'product')
   * @param entityId - Entity ID
   * @param metaKey - Metadata key
   * @param updateFn - Synchronous function to transform current value
   * @param userId - User ID for RLS context
   * @param options - Transaction options
   * @returns Updated value
   *
   * @throws TransactionalError if update fails
   * @throws Error if updateFn is async (holds lock during I/O)
   */
  static async updateWithLock(entityType, entityId, metaKey, updateFn, userId, options = {}) {
    var _a;
    if (updateFn.constructor.name === "AsyncFunction") {
      throw new Error(
        "updateFn must be synchronous to avoid holding locks during I/O operations"
      );
    }
    const opts = { ...this.DEFAULT_OPTIONS, ...options };
    const config = getEntityMetaConfig(entityType);
    if (!config) {
      throw new TransactionalError(`Entity type '${entityType}' not configured`);
    }
    if (!metaKey || metaKey.trim() === "") {
      throw new TransactionalError("Meta key cannot be empty");
    }
    if (metaKey.length > 100) {
      throw new TransactionalError("Meta key too long (max 100 characters)");
    }
    let lastError = null;
    for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
      const client = await pool.connect();
      try {
        await client.query("BEGIN");
        await client.query(
          `SET LOCAL app.user_id = '${userId.replace(/'/g, "''")}'`
        );
        await client.query(`SET LOCAL lock_timeout = '${opts.lockTimeout}ms'`);
        const selectQuery = `
          SELECT "metaValue"
          FROM "${config.metaTableName}"
          WHERE "${config.idColumn}" = $1 AND "metaKey" = $2
          FOR UPDATE
        `;
        const result = await client.query(selectQuery, [entityId, metaKey]);
        const currentValue = ((_a = result.rows[0]) == null ? void 0 : _a.metaValue) || null;
        const newValue = updateFn(currentValue);
        const jsonString = JSON.stringify(newValue);
        if (new TextEncoder().encode(jsonString).length > 1048576) {
          throw new TransactionalError("Meta value too large (max 1MB)");
        }
        const upsertQuery = `
          INSERT INTO "${config.metaTableName}"
            ("${config.idColumn}", "metaKey", "metaValue", "dataType", "isPublic", "isSearchable")
          VALUES ($1, $2, $3::jsonb, 'json', false, false)
          ON CONFLICT ("${config.idColumn}", "metaKey")
          DO UPDATE SET
            "metaValue" = EXCLUDED."metaValue",
            "updatedAt" = CURRENT_TIMESTAMP
          RETURNING "metaValue"
        `;
        const upsertResult = await client.query(upsertQuery, [
          entityId,
          metaKey,
          newValue
        ]);
        await client.query("COMMIT");
        client.release();
        return upsertResult.rows[0].metaValue;
      } catch (error) {
        await client.query("ROLLBACK");
        client.release();
        const pgError = error;
        lastError = pgError;
        if (pgError.code === "40P01" && opts.retryOnDeadlock && attempt < opts.maxRetries) {
          console.warn(
            `Deadlock detected on attempt ${attempt}/${opts.maxRetries}, retrying...`
          );
          const delay = opts.initialRetryDelay * Math.pow(2, attempt - 1);
          await new Promise((resolve) => setTimeout(resolve, delay));
          continue;
        }
        if (pgError.code === "55P03") {
          throw new TransactionalError(
            `Lock timeout after ${opts.lockTimeout}ms`,
            "55P03",
            pgError
          );
        }
        throw new TransactionalError(
          `Transaction failed: ${pgError.message}`,
          pgError.code,
          pgError
        );
      }
    }
    throw new TransactionalError(
      `Transaction failed after ${opts.maxRetries} attempts`,
      (lastError == null ? void 0 : lastError.constructor.name) === "Error" ? lastError.code : void 0,
      lastError || void 0
    );
  }
  /**
   * Bulk update multiple metadata keys atomically
   *
   * @param entityType - Entity type
   * @param entityId - Entity ID
   * @param updates - Object with metaKey: updateFn pairs
   * @param userId - User ID for RLS context
   * @param options - Transaction options
   * @returns Object with updated values
   */
  static async bulkUpdateWithLock(entityType, entityId, updates, userId, options = {}) {
    const results = {};
    for (const [metaKey, updateFn] of Object.entries(updates)) {
      results[metaKey] = await this.updateWithLock(
        entityType,
        entityId,
        metaKey,
        updateFn,
        userId,
        options
      );
    }
    return results;
  }
  /**
   * Delete metadata with lock
   *
   * @param entityType - Entity type
   * @param entityId - Entity ID
   * @param metaKey - Metadata key to delete
   * @param userId - User ID for RLS context
   * @returns true if deleted, false if not found
   */
  static async deleteWithLock(entityType, entityId, metaKey, userId) {
    const config = getEntityMetaConfig(entityType);
    if (!config) {
      throw new TransactionalError(`Entity type '${entityType}' not configured`);
    }
    const client = await pool.connect();
    try {
      await client.query("BEGIN");
      await client.query(
        `SET LOCAL app.user_id = '${userId.replace(/'/g, "''")}'`
      );
      const deleteQuery = `
        DELETE FROM "${config.metaTableName}"
        WHERE "${config.idColumn}" = $1 AND "metaKey" = $2
        RETURNING "metaKey"
      `;
      const result = await client.query(deleteQuery, [entityId, metaKey]);
      await client.query("COMMIT");
      client.release();
      return (result.rowCount || 0) > 0;
    } catch (error) {
      await client.query("ROLLBACK");
      client.release();
      const pgError = error;
      throw new TransactionalError(
        `Delete failed: ${pgError.message}`,
        pgError.code,
        pgError
      );
    }
  }
}
export {
  TransactionalError,
  TransactionalMetaService
};
