import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { CarrierShipment } from "../data/entities.js";
import { emitShippingEvent } from "../events.js";
import { getShippingAdapter } from "./adapter-registry.js";
import {
  claimShipmentIdempotency,
  computeShipmentRequestHash,
  findShipmentIdempotencyClaim,
  isShipmentIdempotencyConflictError,
  releaseShipmentIdempotency,
  resolveShipmentIdempotency,
  ShipmentIdempotencyConflictError
} from "./shipment-idempotency.js";
import {
  getTerminalShippingEvent,
  isValidShippingTransition,
  ShipmentCancelNotAllowedError,
  syncShipmentStatus,
  TERMINAL_SHIPPING_STATUSES
} from "./status-sync.js";
class CarrierShipmentNotFoundError extends Error {
  constructor() {
    super("Shipment not found");
    this.name = "CarrierShipmentNotFoundError";
  }
}
function createShippingCarrierService(deps) {
  const { em, integrationCredentialsService } = deps;
  async function findShipmentOrThrow(shipmentId, scope) {
    const shipment = await findOneWithDecryption(
      em,
      CarrierShipment,
      {
        id: shipmentId,
        organizationId: scope.organizationId,
        tenantId: scope.tenantId,
        deletedAt: null
      },
      void 0,
      scope
    );
    if (!shipment) {
      throw new CarrierShipmentNotFoundError();
    }
    return shipment;
  }
  async function findShipmentByTrackingNumberOrThrow(providerKey, trackingNumber, scope) {
    const shipment = await findOneWithDecryption(
      em,
      CarrierShipment,
      {
        providerKey,
        trackingNumber,
        organizationId: scope.organizationId,
        tenantId: scope.tenantId,
        deletedAt: null
      },
      void 0,
      scope
    );
    if (!shipment) {
      throw new CarrierShipmentNotFoundError();
    }
    return shipment;
  }
  async function resolveAdapter(providerKey, scope) {
    const adapter = getShippingAdapter(providerKey);
    if (!adapter) throw new Error(`No shipping adapter registered for provider: ${providerKey}`);
    const credentials = await integrationCredentialsService.resolve(`carrier_${providerKey}`, scope) ?? {};
    return { adapter, credentials };
  }
  return {
    async calculateRates(input) {
      const { adapter, credentials } = await resolveAdapter(input.providerKey, {
        organizationId: input.organizationId,
        tenantId: input.tenantId
      });
      const mergedCredentials = {
        ...credentials,
        ...input.receiverPhone !== void 0 ? { receiverPhone: input.receiverPhone } : {},
        ...input.receiverEmail !== void 0 ? { receiverEmail: input.receiverEmail } : {}
      };
      return adapter.calculateRates({
        origin: input.origin,
        destination: input.destination,
        packages: input.packages,
        credentials: mergedCredentials
      });
    },
    async createShipment(input) {
      const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
      let claim = null;
      if (input.idempotencyKey) {
        const { idempotencyKey: _idempotencyKey, ...fingerprint } = input;
        const requestHash = computeShipmentRequestHash(fingerprint);
        const existing = await findShipmentIdempotencyClaim(em, input.idempotencyKey, input.providerKey, scope);
        if (existing) {
          if (existing.requestHash !== requestHash) {
            throw new ShipmentIdempotencyConflictError(input.idempotencyKey);
          }
          if (existing.shipmentId) {
            return findShipmentOrThrow(existing.shipmentId, scope);
          }
          throw new ShipmentIdempotencyConflictError(input.idempotencyKey);
        }
        claim = await claimShipmentIdempotency(em, input.idempotencyKey, input.providerKey, requestHash, scope);
        if (!claim) {
          throw new ShipmentIdempotencyConflictError(input.idempotencyKey);
        }
      }
      let carrierShipmentCreated = false;
      try {
        const { adapter, credentials } = await resolveAdapter(input.providerKey, scope);
        const mergedCredentials = {
          ...credentials,
          ...input.senderPhone !== void 0 ? { senderPhone: input.senderPhone } : {},
          ...input.senderEmail !== void 0 ? { senderEmail: input.senderEmail } : {},
          ...input.receiverPhone !== void 0 ? { receiverPhone: input.receiverPhone } : {},
          ...input.receiverEmail !== void 0 ? { receiverEmail: input.receiverEmail } : {},
          ...input.targetPoint !== void 0 ? { targetPoint: input.targetPoint } : {},
          ...input.c2cSendingMethod !== void 0 ? { c2cSendingMethod: input.c2cSendingMethod } : {}
        };
        const created = await adapter.createShipment({
          orderId: input.orderId,
          origin: input.origin,
          destination: input.destination,
          packages: input.packages,
          serviceCode: input.serviceCode,
          credentials: mergedCredentials,
          labelFormat: input.labelFormat
        });
        carrierShipmentCreated = true;
        const shipment = em.create(CarrierShipment, {
          orderId: input.orderId,
          providerKey: input.providerKey,
          carrierShipmentId: created.shipmentId,
          trackingNumber: created.trackingNumber,
          unifiedStatus: "label_created",
          labelUrl: created.labelUrl ?? null,
          labelData: created.labelData ?? null,
          organizationId: input.organizationId,
          tenantId: input.tenantId
        });
        await em.persist(shipment).flush();
        if (claim) {
          await resolveShipmentIdempotency(em, claim, shipment.id);
        }
        await emitShippingEvent("shipping_carriers.shipment.created", {
          shipmentId: shipment.id,
          orderId: input.orderId,
          providerKey: input.providerKey,
          trackingNumber: created.trackingNumber,
          organizationId: input.organizationId,
          tenantId: input.tenantId
        });
        return shipment;
      } catch (error) {
        if (claim && !carrierShipmentCreated && input.idempotencyKey && !isShipmentIdempotencyConflictError(error)) {
          await releaseShipmentIdempotency(em, input.idempotencyKey, input.providerKey, scope).catch(() => {
          });
        }
        throw error;
      }
    },
    async getTracking(input) {
      const scope = {
        organizationId: input.organizationId,
        tenantId: input.tenantId
      };
      const { adapter, credentials } = await resolveAdapter(input.providerKey, scope);
      let shipment;
      if (input.shipmentId) {
        shipment = await findShipmentOrThrow(input.shipmentId, scope);
      } else {
        const trackingNumber = input.trackingNumber;
        if (!trackingNumber) {
          throw new CarrierShipmentNotFoundError();
        }
        shipment = await findShipmentByTrackingNumberOrThrow(input.providerKey, trackingNumber, scope);
      }
      return adapter.getTracking({
        shipmentId: shipment.carrierShipmentId,
        trackingNumber: shipment.trackingNumber,
        credentials
      });
    },
    async refreshTracking(input) {
      const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
      const shipment = await findShipmentOrThrow(input.shipmentId, scope);
      const { adapter, credentials } = await resolveAdapter(input.providerKey, scope);
      const tracking = await adapter.getTracking({
        shipmentId: shipment.carrierShipmentId,
        trackingNumber: shipment.trackingNumber,
        credentials
      });
      const previousStatus = shipment.unifiedStatus;
      shipment.trackingEvents = tracking.events;
      shipment.lastPolledAt = /* @__PURE__ */ new Date();
      const transitionApplied = syncShipmentStatus(shipment, tracking.status);
      await em.flush();
      if (transitionApplied) {
        const eventPayload = {
          shipmentId: shipment.id,
          providerKey: input.providerKey,
          previousStatus,
          newStatus: tracking.status,
          organizationId: input.organizationId,
          tenantId: input.tenantId
        };
        await emitShippingEvent("shipping_carriers.shipment.status_changed", eventPayload);
        if (TERMINAL_SHIPPING_STATUSES.has(tracking.status)) {
          const terminalEvent = getTerminalShippingEvent(tracking.status);
          if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload);
        }
      }
      return tracking;
    },
    async cancelShipment(input) {
      const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
      const shipment = await findShipmentOrThrow(input.shipmentId, scope);
      if (!isValidShippingTransition(shipment.unifiedStatus, "cancelled")) {
        throw new ShipmentCancelNotAllowedError(shipment.unifiedStatus);
      }
      const { adapter, credentials } = await resolveAdapter(input.providerKey, {
        organizationId: input.organizationId,
        tenantId: input.tenantId
      });
      const result = await adapter.cancelShipment({
        shipmentId: shipment.carrierShipmentId,
        reason: input.reason,
        credentials
      });
      shipment.unifiedStatus = result.status;
      await em.flush();
      await emitShippingEvent("shipping_carriers.shipment.cancelled", {
        shipmentId: shipment.id,
        providerKey: input.providerKey,
        organizationId: input.organizationId,
        tenantId: input.tenantId
      });
      return result;
    },
    async findShipmentByCarrierId(providerKey, carrierShipmentId, scope) {
      return findOneWithDecryption(
        em,
        CarrierShipment,
        {
          providerKey,
          carrierShipmentId,
          organizationId: scope.organizationId,
          tenantId: scope.tenantId,
          deletedAt: null
        },
        void 0,
        scope
      );
    },
    async searchDropOffPoints(input) {
      const { adapter, credentials } = await resolveAdapter(input.providerKey, {
        organizationId: input.organizationId,
        tenantId: input.tenantId
      });
      if (!adapter.searchDropOffPoints) {
        throw new Error(`Provider ${input.providerKey} does not support drop-off point search`);
      }
      return adapter.searchDropOffPoints({
        query: input.query,
        type: input.type,
        postCode: input.postCode,
        credentials
      });
    }
  };
}
export {
  CarrierShipmentNotFoundError,
  createShippingCarrierService
};
//# sourceMappingURL=shipping-service.js.map
