import { describe, expect, it } from 'vitest';
import { OrderService } from '../application/order-service.js';
import { OrderStateTransitionError } from '../domain/errors.js';
import type { OrderEvent, OrderEventPublisher, OrderRepository } from '../domain/ports.js';
import type { OrderItemInput, OrderState, OrderView } from '../domain/order.js';

const ORDER_VIEW: OrderView = {
  id: 'order-1',
  userId: 'user-1',
  idempotencyKey: null,
  state: 'PENDING',
  currency: 'EUR',
  subtotalCents: 1000,
  discountCents: 0,
  taxCents: 210,
  totalCents: 1210,
  createdAt: new Date('2026-01-01T00:00:00Z'),
  updatedAt: new Date('2026-01-01T00:00:00Z'),
  items: [
    {
      id: 'item-1',
      orderId: 'order-1',
      productId: 'product-1',
      variantId: 'variant-1',
      sku: 'SKU-1',
      ean: null,
      name: 'Product One',
      unitPriceCents: 1000,
      discountCents: 0,
      taxCents: 210,
      quantity: 1,
      createdAt: new Date('2026-01-01T00:00:00Z'),
    },
  ],
};

function repo(overrides: Partial<OrderRepository> = {}): OrderRepository {
  return {
    create: async () => ORDER_VIEW,
    findAll: async () => [ORDER_VIEW],
    findById: async () => ORDER_VIEW,
    findByIdAndUserId: async () => ORDER_VIEW,
    updateState: async (_id, state) => ({ ...ORDER_VIEW, state: state as OrderState }),
    ...overrides,
  };
}

class CapturePublisher implements OrderEventPublisher {
  events: OrderEvent[] = [];
  async emit(event: OrderEvent): Promise<void> {
    this.events.push(event);
  }
}

describe('OrderService', () => {
  it('creates an order and emits OrderCreated', async () => {
    const publisher = new CapturePublisher();
    const service = new OrderService(repo(), publisher);
    const order = await service.create({
      userId: 'user-1',
      idempotencyKey: null,
      items: [
        {
          productId: 'product-1',
          variantId: 'variant-1',
          sku: 'SKU-1',
          ean: null,
          name: 'Product One',
          unitPriceCents: 1000,
          discountCents: 0,
          taxCents: 210,
          quantity: 1,
        } satisfies OrderItemInput,
      ],
      totals: { subtotalCents: 1000, discountCents: 0, taxCents: 210, totalCents: 1210 },
    });
    expect(order.id).toBe('order-1');
    expect(publisher.events).toEqual([
      { type: 'OrderCreated', orderId: 'order-1', userId: 'user-1' },
    ]);
  });

  it('rejects illegal state transitions', async () => {
    const publisher = new CapturePublisher();
    const repoWithState: OrderRepository = repo({
      findByIdAndUserId: async () => ({ ...ORDER_VIEW, state: 'SHIPPED' }),
    });
    const service = new OrderService(repoWithState, publisher);
    await expect(service.transition('order-1', 'PENDING', 'user-1')).rejects.toBeInstanceOf(
      OrderStateTransitionError,
    );
  });

  it('passes tracking number and courier to the repository on admin transition', async () => {
    const calls: Array<{ state: OrderState; trackingNumber?: string; courier?: string }> = [];
    const repoCapturing: OrderRepository = repo({
      findById: async () => ({ ...ORDER_VIEW, state: 'PROCESSING' }),
      updateState: async (_id, state, trackingNumber, courier) => {
        calls.push({ state, trackingNumber, courier });
        return { ...ORDER_VIEW, state, trackingNumber: trackingNumber ?? null, courier: courier ?? null };
      },
    });
    const service = new OrderService(repoCapturing, new CapturePublisher());
    const updated = await service.transitionAdmin('order-1', 'SHIPPED', 'TRK-1', 'SEUR');
    expect(calls).toEqual([{ state: 'SHIPPED', trackingNumber: 'TRK-1', courier: 'SEUR' }]);
    expect(updated.state).toBe('SHIPPED');
    expect(updated.trackingNumber).toBe('TRK-1');
    expect(updated.courier).toBe('SEUR');
  });
});
