import { DEFAULT_STORE_ID, type InventoryServicePort } from '../../inventory/index.js';
import type { OrderItemInput, OrderServicePort } from '../../orders/index.js';
import type { PricingServicePort } from '../../pricing/index.js';
import type { PromotionServicePort } from '../../promotions/index.js';
import type { ShippingAddress, ShippingServicePort } from '../../shipping/index.js';
import { CheckoutError, type CheckoutResult, type PaymentIntent } from '../domain/checkout.js';
import type { Tracer } from '../../observability/index.js';
import type { CheckoutMetrics, PaymentProvider, CheckoutOrderLookup } from '../domain/ports.js';

export interface CartLike {
  userId: string;
  items: Array<{ productId: string; variantId: string; quantity: number }>;
}

export interface CartServicePort {
  getCart(userId: string): Promise<CartLike>;
}

export interface CheckoutServiceDeps {
  cart: CartServicePort;
  pricing: PricingServicePort;
  promotions?: PromotionServicePort;
  inventory: InventoryServicePort;
  shipping: ShippingServicePort;
  orders: OrderServicePort;
  payments: PaymentProvider;
  orderLookup: CheckoutOrderLookup;
  metrics: CheckoutMetrics;
  tracer?: Tracer;
  /** Peso total del carrito en kg (cantidad × peso unitario del producto). */
  getCartWeightKg?: (
    items: Array<{ productId: string; quantity: number }>,
  ) => Promise<number>;
}

export interface CheckoutCommand {
  userId: string;
  address: ShippingAddress;
  promoCode?: string | null;
  idempotencyKey: string;
}

export class CheckoutService {
  constructor(private readonly deps: CheckoutServiceDeps) {}

  async execute(command: CheckoutCommand): Promise<CheckoutResult> {
    const span = this.deps.tracer?.startSpan('checkout.execute') ?? {
      end: () => undefined,
      setError: () => undefined,
    };
    try {
      const idempotentOrderId = (
        await this.deps.orderLookup.findByIdempotency(command.userId, command.idempotencyKey)
      )?.orderId;
      if (idempotentOrderId) {
        const existing = await this.deps.orders.getOrder(idempotentOrderId, command.userId);
        if (!existing)
          throw new CheckoutError(
            'CHECKOUT_IDEMPOTENCY_CONFLICT',
            'Idempotency key already used',
            409,
          );
        return {
          order: existing,
          paymentIntent: {
            id: `pi_${idempotentOrderId}`,
            reference: `ref_${idempotentOrderId}`,
            status: 'requires_payment',
          },
          reservedVariantIds: existing.items.map((item) => item.variantId),
        };
      }

      const cart = await this.deps.cart.getCart(command.userId);
      if (cart.items.length === 0) {
        this.deps.metrics.incFailure();
        throw new CheckoutError('CHECKOUT_CART_EMPTY', 'Cart is empty', 409);
      }

      let netSubtotalCents = 0;
      let taxCents = 0;
      const itemInputs: OrderItemInput[] = [];
      for (const cartItem of cart.items) {
        const calculation = await this.deps.pricing
          .calculate({ variantId: cartItem.variantId, quantity: cartItem.quantity })
          .catch((error: unknown) => {
            if (error instanceof Error && error.name === 'PriceNotFoundError') return null;
            throw error;
          });
        if (!calculation) {
          this.deps.metrics.incFailure();
          throw new CheckoutError(
            'CHECKOUT_PRICE_MISSING',
            `Price missing for variant ${cartItem.variantId}`,
            409,
          );
        }
        const availability = await this.deps.inventory.checkAvailability(
          cartItem.variantId,
          DEFAULT_STORE_ID,
          cartItem.quantity,
        );
        if (!availability.available) {
          this.deps.metrics.incFailure();
          throw new CheckoutError(
            'CHECKOUT_STOCK_UNAVAILABLE',
            `Variant ${cartItem.variantId} is out of stock`,
            409,
          );
        }
        netSubtotalCents += calculation.netSubtotalCents;
        taxCents += calculation.vatAmountCents;
        itemInputs.push({
          productId: cartItem.productId,
          variantId: cartItem.variantId,
          sku: cartItem.variantId,
          ean: null,
          name: `Variant ${cartItem.variantId.slice(0, 8)}`,
          unitPriceCents: calculation.netUnitAmountCents,
          discountCents: 0,
          taxCents: calculation.vatAmountCents,
          quantity: cartItem.quantity,
        });
      }

      let discountCents = 0;
      if (command.promoCode && this.deps.promotions) {
        try {
          const applied = await this.deps.promotions.calculateDiscount(
            command.promoCode,
            netSubtotalCents + taxCents,
          );
          discountCents = applied.discountCents;
        } catch (error) {
          this.deps.metrics.incFailure();
          throw new CheckoutError('CHECKOUT_PROMO_INVALID', (error as Error).message, 422);
        }
      }

      const cartWeightKg = this.deps.getCartWeightKg
        ? await this.deps.getCartWeightKg(
            cart.items.map((item) => ({ productId: item.productId, quantity: item.quantity })),
          ).catch(() => 0)
        : 0;

      const shipping = await this.deps.shipping
        .calculate(
          Math.max(0, netSubtotalCents + taxCents - discountCents),
          command.address,
          cartWeightKg,
        )
        .catch((error: unknown) => {
          if (error instanceof Error && error.name === 'ShippingZoneNotFoundError') return null;
          throw error;
        });
      if (!shipping) {
        this.deps.metrics.incFailure();
        throw new CheckoutError(
          'CHECKOUT_SHIPPING_ZONE_NOT_FOUND',
          'Shipping zone not found for address',
          422,
        );
      }

      const subtotalCents = netSubtotalCents + taxCents;
      const totalCents = Math.max(0, subtotalCents - discountCents) + shipping.costCents;

      const orderView = await this.deps.orders.create({
        userId: command.userId,
        idempotencyKey: command.idempotencyKey,
        items: itemInputs,
        totals: { subtotalCents, discountCents, taxCents, totalCents },
      });

      const reserved: string[] = [];
      try {
        for (const item of cart.items) {
          await this.deps.inventory.reserve({
            variantId: item.variantId,
            storeId: DEFAULT_STORE_ID,
            quantity: item.quantity,
          });
          reserved.push(item.variantId);
        }
      } catch (error) {
        for (const variantId of reserved) {
          await this.deps.inventory
            .release({ variantId, storeId: DEFAULT_STORE_ID, quantity: 1 })
            .catch(() => undefined);
        }
        await this.deps.orders
          .transition(orderView.id, 'CANCELLED', command.userId)
          .catch(() => undefined);
        this.deps.metrics.incFailure();
        throw new CheckoutError('CHECKOUT_RESERVATION_FAILED', (error as Error).message, 409);
      }

      await this.deps.orders
        .transition(orderView.id, 'AWAITING_PAYMENT', command.userId)
        .catch(() => undefined);
      const refreshed =
        (await this.deps.orders.getOrder(orderView.id, command.userId)) ?? orderView;

      const paymentIntent: PaymentIntent = await this.deps.payments.createIntent({
        orderId: refreshed.id,
        amountCents: refreshed.totalCents,
        currency: 'EUR',
      });

      this.deps.metrics.incSuccess();
      this.deps.metrics.checkoutCompleted(totalCents);
      return { order: refreshed, paymentIntent, reservedVariantIds: reserved };
    } catch (error) {
      span.setError(error);
      throw error;
    } finally {
      span.end();
    }
  }
}
