import { registerCommand } from "@open-mercato/shared/lib/commands";
import { buildChanges, requireId, emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
import { makeCreateRedo } from "@open-mercato/shared/lib/commands/redo";
import { CrudHttpError, conflict, isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { ExchangeRate, Currency } from "../data/entities.js";
import {
  exchangeRateCreateSchema,
  exchangeRateUpdateSchema,
  exchangeRateDeleteSchema
} from "../data/validators.js";
const exchangeRateCrudEvents = {
  module: "currencies",
  entity: "exchange_rate",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId
  })
};
async function loadExchangeRateSnapshot(em, id) {
  const record = await em.findOne(ExchangeRate, { id });
  if (!record) return null;
  return {
    id: record.id,
    organizationId: record.organizationId,
    tenantId: record.tenantId,
    fromCurrencyCode: record.fromCurrencyCode,
    toCurrencyCode: record.toCurrencyCode,
    rate: record.rate,
    date: record.date.toISOString(),
    source: record.source,
    type: record.type ?? null,
    isActive: !!record.isActive,
    createdAt: record.createdAt.toISOString(),
    updatedAt: record.updatedAt.toISOString()
  };
}
async function validateCurrenciesExist(em, fromCode, toCode, organizationId, tenantId) {
  const fromCurrency = await em.findOne(Currency, {
    code: fromCode,
    organizationId,
    tenantId,
    deletedAt: null
  });
  if (!fromCurrency) {
    throw new CrudHttpError(400, { error: `From currency ${fromCode} does not exist or is inactive` });
  }
  const toCurrency = await em.findOne(Currency, {
    code: toCode,
    organizationId,
    tenantId,
    deletedAt: null
  });
  if (!toCurrency) {
    throw new CrudHttpError(400, { error: `To currency ${toCode} does not exist or is inactive` });
  }
}
const createExchangeRateCommand = {
  id: "currencies.exchange_rates.create",
  async execute(input, ctx) {
    const parsed = exchangeRateCreateSchema.parse(input);
    const em = ctx.container.resolve("em").fork();
    await validateCurrenciesExist(em, parsed.fromCurrencyCode, parsed.toCurrencyCode, parsed.organizationId, parsed.tenantId);
    const existing = await em.findOne(ExchangeRate, {
      fromCurrencyCode: parsed.fromCurrencyCode,
      toCurrencyCode: parsed.toCurrencyCode,
      date: parsed.date,
      source: parsed.source,
      organizationId: parsed.organizationId,
      tenantId: parsed.tenantId,
      deletedAt: null
    });
    if (existing) {
      throw conflict("Exchange rate for this currency pair, date, and source already exists");
    }
    const now = /* @__PURE__ */ new Date();
    const record = em.create(ExchangeRate, {
      organizationId: parsed.organizationId,
      tenantId: parsed.tenantId,
      fromCurrencyCode: parsed.fromCurrencyCode,
      toCurrencyCode: parsed.toCurrencyCode,
      rate: parsed.rate,
      date: parsed.date,
      source: parsed.source,
      type: parsed.type ?? null,
      isActive: parsed.isActive !== false,
      createdAt: now,
      updatedAt: now
    });
    em.persist(record);
    try {
      await em.flush();
    } catch (err) {
      if (isUniqueViolation(err, "exchange_rates_pair_datetime_source_unique")) {
        throw conflict("Exchange rate for this currency pair, date, and source already exists");
      }
      throw err;
    }
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: record,
      identifiers: {
        id: record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      events: exchangeRateCrudEvents
    });
    return { exchangeRateId: record.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return loadExchangeRateSnapshot(em, result.exchangeRateId);
  },
  buildLog: async ({ snapshots }) => {
    const after = snapshots.after;
    if (!after) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("currencies.rates.audit.create", "Create exchange rate"),
      resourceKind: "currencies.exchange_rate",
      resourceId: after.id,
      tenantId: after.tenantId,
      organizationId: after.organizationId,
      snapshotAfter: after,
      payload: { undo: { after } }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const after = payload?.after ?? null;
    if (!after) return;
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(ExchangeRate, { id: after.id });
    if (!record) return;
    record.deletedAt = /* @__PURE__ */ new Date();
    record.isActive = false;
    await em.flush();
  },
  redo: makeCreateRedo({
    entityClass: ExchangeRate,
    dateFields: ["createdAt", "updatedAt", "deletedAt", "date"],
    buildResult: (entity) => ({ exchangeRateId: entity.id }),
    events: exchangeRateCrudEvents
  })
};
const updateExchangeRateCommand = {
  id: "currencies.exchange_rates.update",
  async prepare(input, ctx) {
    requireId(input.id, "Exchange rate ID is required");
    const em = ctx.container.resolve("em");
    const before = await loadExchangeRateSnapshot(em, input.id);
    return { before };
  },
  async execute(input, ctx) {
    const parsed = exchangeRateUpdateSchema.parse(input);
    requireId(parsed.id, "Exchange rate ID is required");
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(ExchangeRate, { id: parsed.id, deletedAt: null });
    if (!record) {
      throw new CrudHttpError(404, { error: "Exchange rate not found" });
    }
    const fromCode = parsed.fromCurrencyCode ?? record.fromCurrencyCode;
    const toCode = parsed.toCurrencyCode ?? record.toCurrencyCode;
    if (parsed.fromCurrencyCode || parsed.toCurrencyCode) {
      await validateCurrenciesExist(em, fromCode, toCode, record.organizationId, record.tenantId);
    }
    if (parsed.fromCurrencyCode || parsed.toCurrencyCode || parsed.date || parsed.source) {
      const date = parsed.date ?? record.date;
      const source = parsed.source ?? record.source;
      const existing = await em.findOne(ExchangeRate, {
        fromCurrencyCode: fromCode,
        toCurrencyCode: toCode,
        date,
        source,
        organizationId: record.organizationId,
        tenantId: record.tenantId,
        id: { $ne: record.id },
        deletedAt: null
      });
      if (existing) {
        throw conflict("Exchange rate for this currency pair, date, and source already exists");
      }
    }
    const allChanges = buildChanges(record, parsed, [
      "fromCurrencyCode",
      "toCurrencyCode",
      "rate",
      "date",
      "source",
      "type",
      "isActive"
    ]);
    const changes = Object.fromEntries(
      Object.entries(allChanges).filter(([, c]) => c.to !== void 0)
    );
    if (Object.keys(changes).length === 0) {
      return { exchangeRateId: record.id };
    }
    for (const [key, change] of Object.entries(changes)) {
      ;
      record[key] = change.to;
    }
    record.updatedAt = /* @__PURE__ */ new Date();
    if (record.fromCurrencyCode === record.toCurrencyCode) {
      throw new CrudHttpError(400, { error: "From and To currencies must be different" });
    }
    const rateValue = parseFloat(record.rate);
    if (isNaN(rateValue) || rateValue <= 0) {
      throw new CrudHttpError(400, { error: "Rate must be greater than zero" });
    }
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: record,
      identifiers: {
        id: record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      events: exchangeRateCrudEvents
    });
    return { exchangeRateId: record.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return loadExchangeRateSnapshot(em, result.exchangeRateId);
  },
  buildLog: async ({ snapshots, result }) => {
    const before = snapshots.before;
    const after = snapshots.after;
    if (!after) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("currencies.rates.audit.update", "Update exchange rate"),
      resourceKind: "currencies.exchange_rate",
      resourceId: after.id,
      tenantId: after.tenantId,
      organizationId: after.organizationId,
      snapshotBefore: before ?? void 0,
      snapshotAfter: after,
      payload: { undo: { before, after } }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before ?? null;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(ExchangeRate, { id: before.id });
    if (!record) return;
    Object.assign(record, {
      fromCurrencyCode: before.fromCurrencyCode,
      toCurrencyCode: before.toCurrencyCode,
      rate: before.rate,
      date: new Date(before.date),
      source: before.source,
      type: before.type,
      isActive: before.isActive,
      updatedAt: /* @__PURE__ */ new Date()
    });
    await em.flush();
  }
};
const deleteExchangeRateCommand = {
  id: "currencies.exchange_rates.delete",
  async prepare(input, ctx) {
    requireId(input.id, "Exchange rate ID is required");
    const em = ctx.container.resolve("em");
    const before = await loadExchangeRateSnapshot(em, input.id);
    return { before };
  },
  async execute(input, ctx) {
    const parsed = exchangeRateDeleteSchema.parse(input);
    requireId(parsed.id, "Exchange rate ID is required");
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(ExchangeRate, { id: parsed.id, deletedAt: null });
    if (!record) {
      throw new CrudHttpError(404, { error: "Exchange rate not found" });
    }
    record.deletedAt = /* @__PURE__ */ new Date();
    record.isActive = false;
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: record,
      identifiers: {
        id: record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      events: exchangeRateCrudEvents
    });
    return { exchangeRateId: record.id };
  },
  buildLog: async ({ snapshots, result }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("currencies.rates.audit.delete", "Delete exchange rate"),
      resourceKind: "currencies.exchange_rate",
      resourceId: before.id,
      tenantId: before.tenantId,
      organizationId: before.organizationId,
      snapshotBefore: before,
      payload: { undo: { before } }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before ?? null;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(ExchangeRate, { id: before.id });
    if (!record) return;
    record.deletedAt = null;
    record.isActive = before.isActive;
    record.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
  }
};
registerCommand(createExchangeRateCommand);
registerCommand(updateExchangeRateCommand);
registerCommand(deleteExchangeRateCommand);
export {
  createExchangeRateCommand,
  deleteExchangeRateCommand,
  updateExchangeRateCommand
};
//# sourceMappingURL=exchange-rates.js.map
