import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import {
  getGatewayAdapter
} from "@open-mercato/shared/modules/payment_gateways/types";
import { conflict } from "@open-mercato/shared/lib/crud/errors";
import { GatewayTransaction } from "../data/entities.js";
import { canApplyManualAction, isValidTransition } from "./status-machine.js";
import { emitPaymentGatewayEvent } from "../events.js";
import { readGatewayMetadata, readWebhookLog } from "./transaction-fields.js";
function assertManualActionAllowed(action, transaction) {
  const current = transaction.unifiedStatus;
  if (!canApplyManualAction(action, current)) {
    throw conflict(`Cannot ${action} a payment in status "${current}"`);
  }
}
function applyAdapterResultStatus(action, transaction, resultStatus) {
  const current = transaction.unifiedStatus;
  if (resultStatus === current) return false;
  if (!isValidTransition(current, resultStatus)) {
    throw conflict(
      `Gateway returned status "${resultStatus}" which is not a valid transition from "${current}" for ${action}`
    );
  }
  transaction.unifiedStatus = resultStatus;
  return true;
}
function createPaymentGatewayService(deps) {
  const { em, integrationCredentialsService, integrationLogService } = deps;
  async function findTransactionOrThrow(transactionId, scope) {
    const transaction = await findOneWithDecryption(
      em,
      GatewayTransaction,
      {
        id: transactionId,
        organizationId: scope.organizationId,
        tenantId: scope.tenantId,
        deletedAt: null
      },
      void 0,
      scope
    );
    if (!transaction) {
      throw new Error("Transaction not found");
    }
    return transaction;
  }
  function readProviderSessionId(transaction) {
    if (typeof transaction.providerSessionId === "string" && transaction.providerSessionId.trim().length > 0) {
      return transaction.providerSessionId;
    }
    throw new Error("Transaction is missing provider session id");
  }
  async function emitStatusEvent(status, payload) {
    const eventMap = {
      authorized: "payment_gateways.payment.authorized",
      captured: "payment_gateways.payment.captured",
      failed: "payment_gateways.payment.failed",
      refunded: "payment_gateways.payment.refunded",
      cancelled: "payment_gateways.payment.cancelled"
    };
    const eventId = eventMap[status];
    if (!eventId) return;
    await emitPaymentGatewayEvent(eventId, payload);
  }
  async function writeTransactionLog(providerKey, scope, transactionId, level, message, payload, code) {
    if (!integrationLogService) return;
    await integrationLogService.write({
      integrationId: `gateway_${providerKey}`,
      scopeEntityType: "payment_transaction",
      scopeEntityId: transactionId,
      level,
      message,
      code,
      payload: payload ?? null
    }, scope);
  }
  async function resolveAdapterAndCredentials(providerKey, scope) {
    const integrationId = `gateway_${providerKey}`;
    const selectedVersion = deps.integrationStateService ? await deps.integrationStateService.resolveApiVersion(integrationId, scope) : void 0;
    const adapter = getGatewayAdapter(providerKey, selectedVersion);
    if (!adapter) {
      throw new Error(
        selectedVersion ? `No gateway adapter registered for provider: ${providerKey} (version: ${selectedVersion})` : `No gateway adapter registered for provider: ${providerKey}`
      );
    }
    const credentials = await integrationCredentialsService.resolve(integrationId, scope) ?? {};
    return { adapter, credentials };
  }
  return {
    async createPaymentSession(input) {
      const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
      const { adapter, credentials } = await resolveAdapterAndCredentials(input.providerKey, scope);
      const sessionInput = {
        paymentId: input.paymentId,
        orderId: input.orderId,
        tenantId: input.tenantId,
        organizationId: input.organizationId,
        amount: input.amount,
        currencyCode: input.currencyCode,
        captureMethod: input.captureMethod,
        paymentTypes: input.paymentTypes,
        description: input.description,
        successUrl: input.successUrl,
        cancelUrl: input.cancelUrl,
        metadata: input.metadata,
        presentation: input.presentation,
        credentials
      };
      const session = await adapter.createSession(sessionInput);
      const transaction = em.create(GatewayTransaction, {
        paymentId: input.paymentId,
        providerKey: input.providerKey,
        providerSessionId: session.sessionId,
        unifiedStatus: session.status,
        redirectUrl: session.redirectUrl ?? (session.clientSession?.type === "redirect" ? session.clientSession.redirectUrl : null),
        clientSecret: session.clientSecret ?? null,
        amount: String(input.amount),
        currencyCode: input.currencyCode,
        gatewayMetadata: {
          ...session.providerData ?? {},
          ...session.clientSession ? { clientSession: session.clientSession } : {}
        },
        organizationId: input.organizationId,
        tenantId: input.tenantId
      });
      await em.persist(transaction).flush();
      await emitPaymentGatewayEvent("payment_gateways.session.created", {
        transactionId: transaction.id,
        paymentId: transaction.paymentId,
        providerKey: transaction.providerKey,
        status: transaction.unifiedStatus,
        organizationId: transaction.organizationId,
        tenantId: transaction.tenantId
      });
      await writeTransactionLog(
        transaction.providerKey,
        scope,
        transaction.id,
        "info",
        "Payment session created",
        {
          paymentId: transaction.paymentId,
          providerSessionId: transaction.providerSessionId,
          status: transaction.unifiedStatus,
          amount: input.amount,
          currencyCode: input.currencyCode
        }
      );
      return { transaction, session };
    },
    async capturePayment(transactionId, amount, scope) {
      const transaction = await findTransactionOrThrow(transactionId, scope);
      assertManualActionAllowed("capture", transaction);
      const { adapter, credentials } = await resolveAdapterAndCredentials(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
      );
      const result = await adapter.capture({
        sessionId: readProviderSessionId(transaction),
        amount,
        credentials
      });
      const statusChanged = applyAdapterResultStatus("capture", transaction, result.status);
      transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), captureResult: result.providerData };
      await em.flush();
      if (statusChanged) {
        await emitStatusEvent(result.status, {
          transactionId: transaction.id,
          paymentId: transaction.paymentId,
          providerKey: transaction.providerKey,
          organizationId: transaction.organizationId,
          tenantId: transaction.tenantId
        });
      }
      await writeTransactionLog(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId },
        transaction.id,
        "info",
        "Payment captured",
        {
          amount: amount ?? null,
          status: result.status,
          capturedAmount: result.capturedAmount
        }
      );
      return result;
    },
    async refundPayment(transactionId, amount, reason, scope) {
      const transaction = await findTransactionOrThrow(transactionId, scope);
      assertManualActionAllowed("refund", transaction);
      const { adapter, credentials } = await resolveAdapterAndCredentials(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
      );
      const result = await adapter.refund({
        sessionId: readProviderSessionId(transaction),
        amount,
        reason,
        credentials
      });
      const statusChanged = applyAdapterResultStatus("refund", transaction, result.status);
      transaction.gatewayRefundId = result.refundId;
      transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), refundResult: result.providerData };
      await em.flush();
      if (statusChanged) {
        await emitStatusEvent(result.status, {
          transactionId: transaction.id,
          paymentId: transaction.paymentId,
          providerKey: transaction.providerKey,
          organizationId: transaction.organizationId,
          tenantId: transaction.tenantId
        });
      }
      await writeTransactionLog(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId },
        transaction.id,
        "info",
        "Payment refunded",
        {
          amount: amount ?? null,
          reason: reason ?? null,
          status: result.status,
          refundId: result.refundId
        }
      );
      return result;
    },
    async cancelPayment(transactionId, reason, scope) {
      const transaction = await findTransactionOrThrow(transactionId, scope);
      assertManualActionAllowed("cancel", transaction);
      const { adapter, credentials } = await resolveAdapterAndCredentials(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
      );
      const result = await adapter.cancel({
        sessionId: readProviderSessionId(transaction),
        reason,
        credentials
      });
      const statusChanged = applyAdapterResultStatus("cancel", transaction, result.status);
      await em.flush();
      if (statusChanged) {
        await emitStatusEvent(result.status, {
          transactionId: transaction.id,
          paymentId: transaction.paymentId,
          providerKey: transaction.providerKey,
          organizationId: transaction.organizationId,
          tenantId: transaction.tenantId
        });
      }
      await writeTransactionLog(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId },
        transaction.id,
        "info",
        "Payment cancelled",
        {
          reason: reason ?? null,
          status: result.status
        }
      );
      return result;
    },
    async getPaymentStatus(transactionId, scope) {
      const transaction = await findTransactionOrThrow(transactionId, scope);
      const { adapter, credentials } = await resolveAdapterAndCredentials(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
      );
      const status = await adapter.getStatus({
        sessionId: readProviderSessionId(transaction),
        credentials
      });
      if (status.status !== transaction.unifiedStatus && isValidTransition(transaction.unifiedStatus, status.status)) {
        const previousStatus = transaction.unifiedStatus;
        transaction.unifiedStatus = status.status;
        transaction.gatewayStatus = status.status;
        transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), statusResult: status.providerData ?? null };
        transaction.lastPolledAt = /* @__PURE__ */ new Date();
        await em.flush();
        await emitStatusEvent(status.status, {
          transactionId: transaction.id,
          paymentId: transaction.paymentId,
          providerKey: transaction.providerKey,
          previousStatus,
          organizationId: transaction.organizationId,
          tenantId: transaction.tenantId
        });
        await writeTransactionLog(
          transaction.providerKey,
          { organizationId: transaction.organizationId, tenantId: transaction.tenantId },
          transaction.id,
          "info",
          "Payment status updated by poller",
          {
            previousStatus,
            nextStatus: status.status
          }
        );
      }
      return status;
    },
    async syncTransactionStatus(transactionId, update, scope) {
      const transaction = await findTransactionOrThrow(transactionId, scope);
      const currentStatus = transaction.unifiedStatus;
      const canTransition = isValidTransition(currentStatus, update.unifiedStatus);
      const shouldApplyStatus = canTransition && update.unifiedStatus !== currentStatus;
      const previousStatus = transaction.unifiedStatus;
      if (shouldApplyStatus) {
        transaction.unifiedStatus = update.unifiedStatus;
      }
      if (update.providerStatus) {
        transaction.gatewayStatus = update.providerStatus;
      }
      if (update.providerData) {
        transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), ...update.providerData };
      }
      if (update.webhookEvent) {
        const webhookLog = readWebhookLog(transaction.webhookLog);
        webhookLog.push({
          eventType: update.webhookEvent.eventType,
          receivedAt: update.webhookEvent.receivedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
          idempotencyKey: update.webhookEvent.idempotencyKey,
          unifiedStatus: update.unifiedStatus,
          processed: update.webhookEvent.processed
        });
        transaction.webhookLog = webhookLog;
      }
      transaction.lastWebhookAt = /* @__PURE__ */ new Date();
      await em.flush();
      if (shouldApplyStatus) {
        await emitStatusEvent(update.unifiedStatus, {
          transactionId: transaction.id,
          paymentId: transaction.paymentId,
          providerKey: transaction.providerKey,
          previousStatus,
          organizationId: transaction.organizationId,
          tenantId: transaction.tenantId
        });
      }
      await writeTransactionLog(
        transaction.providerKey,
        { organizationId: transaction.organizationId, tenantId: transaction.tenantId },
        transaction.id,
        shouldApplyStatus ? "info" : "warn",
        shouldApplyStatus ? "Payment status synchronized from webhook" : "Webhook received with no status transition",
        {
          previousStatus,
          nextStatus: update.unifiedStatus,
          providerStatus: update.providerStatus ?? null,
          eventType: update.webhookEvent?.eventType ?? null,
          idempotencyKey: update.webhookEvent?.idempotencyKey ?? null
        }
      );
    },
    async findTransaction(id, scope) {
      return findOneWithDecryption(
        em,
        GatewayTransaction,
        {
          id,
          organizationId: scope.organizationId,
          tenantId: scope.tenantId,
          deletedAt: null
        },
        void 0,
        scope
      );
    },
    async findTransactionBySessionId(providerSessionId, scope, providerKey) {
      return findOneWithDecryption(
        em,
        GatewayTransaction,
        {
          providerSessionId,
          organizationId: scope.organizationId,
          tenantId: scope.tenantId,
          deletedAt: null,
          ...providerKey ? { providerKey } : {}
        },
        void 0,
        scope
      );
    },
    async listTransactionsForStatusPolling(scope) {
      const where = {
        unifiedStatus: { $in: ["pending", "authorized", "partially_captured"] },
        deletedAt: null
      };
      if (scope?.organizationId) where.organizationId = scope.organizationId;
      if (scope?.tenantId) where.tenantId = scope.tenantId;
      if (scope?.providerKey) where.providerKey = scope.providerKey;
      return findWithDecryption(
        em,
        GatewayTransaction,
        where,
        {
          orderBy: { updatedAt: "asc" },
          limit: scope?.limit ?? 100
        },
        scope
      );
    }
  };
}
export {
  createPaymentGatewayService
};
//# sourceMappingURL=gateway-service.js.map
