import { OrderNotFoundError, OrderStateTransitionError } from '../domain/errors.js';
import type {
  OrderEventPublisher,
  OrderRepository,
  OrderServicePort,
  CreateOrderCommand,
} from '../domain/ports.js';
import { isTransitionAllowed, type OrderState, type OrderView, type OrderItemInput } from '../domain/order.js';

export class OrderService implements OrderServicePort {
  constructor(
    private readonly repo: OrderRepository,
    private readonly events: OrderEventPublisher,
  ) {}

  async create(input: CreateOrderCommand): Promise<OrderView> {
    const order = await this.repo.create({
      ...input,
      idempotencyKey: input.idempotencyKey ?? null,
    });
    await this.events.emit({ type: 'OrderCreated', orderId: order.id, userId: order.userId });
    return order;
  }

  async listOrders(): Promise<OrderView[]> {
    return this.repo.findAll();
  }

  async searchOrders(filters: {
    state?: string;
    q?: string;
    limit: number;
    offset: number;
  }): Promise<{ items: OrderView[]; total: number }> {
    if (!this.repo.search) {
      const all = await this.repo.findAll();
      return { items: all.slice(filters.offset, filters.offset + filters.limit), total: all.length };
    }
    return this.repo.search(filters);
  }

  /** Edición admin: reemplaza artículos y totales del pedido. */
  async editItems(
    id: string,
    items: OrderItemInput[],
    totals: { subtotalCents: number; discountCents: number; taxCents: number; totalCents: number },
  ): Promise<OrderView> {
    const existing = await this.repo.findById(id);
    if (!existing) throw new OrderNotFoundError();
    if (!this.repo.replaceItems) throw new Error('Order repository does not support item editing');
    const updated = await this.repo.replaceItems(id, items, totals);
    if (!updated) throw new OrderNotFoundError();
    return updated;
  }

  async transition(id: string, next: OrderState, userId: string): Promise<OrderView> {
    const existing = await this.repo.findByIdAndUserId(id, userId);
    if (!existing) throw new OrderNotFoundError();
    if (!isTransitionAllowed(existing.state, next))
      throw new OrderStateTransitionError(existing.state, next);
    const updated = await this.repo.updateState(id, next);
    if (!updated) throw new OrderNotFoundError();
    if (next === 'PAID') await this.events.emit({ type: 'OrderPaid', orderId: id, userId });
    if (next === 'CANCELLED')
      await this.events.emit({ type: 'OrderCancelled', orderId: id, userId });
    return updated;
  }

  async getOrder(id: string, userId: string): Promise<OrderView | undefined> {
    return this.repo.findByIdAndUserId(id, userId);
  }

  async getOrderAdmin(id: string): Promise<OrderView | undefined> {
    return this.repo.findById(id);
  }

  async transitionAdmin(id: string, next: OrderState, trackingNumber?: string, courier?: string): Promise<OrderView> {
    const existing = await this.repo.findById(id);
    if (!existing) throw new OrderNotFoundError();
    if (!isTransitionAllowed(existing.state, next))
      throw new OrderStateTransitionError(existing.state, next);
    const updated = await this.repo.updateState(id, next, trackingNumber, courier);
    if (!updated) throw new OrderNotFoundError();
    if (next === 'PAID')
      await this.events.emit({ type: 'OrderPaid', orderId: id, userId: existing.userId });
    if (next === 'CANCELLED')
      await this.events.emit({ type: 'OrderCancelled', orderId: id, userId: existing.userId });
    return updated;
  }
}
