import { DEFAULT_STORE_ID, type InventoryServicePort } from '../../inventory/index.js';
import type { PricingServicePort } from '../../pricing/index.js';
import type { PromotionServicePort } from '../../promotions/index.js';
import { InvalidCartQuantityError, InsufficientCartStockError } from '../domain/errors.js';
import type { CartItemInput, CartView } from '../domain/cart.js';
import type { CartRepository } from '../domain/ports.js';

export class CartService {
  constructor(
    private readonly carts: CartRepository,
    private readonly pricing: PricingServicePort,
    private readonly inventory: InventoryServicePort,
    private readonly promotions?: PromotionServicePort,
  ) {}

  async getCart(userId: string): Promise<CartView> {
    return this.toView(await this.carts.getOrCreate(userId));
  }

  async addItem(userId: string, input: CartItemInput): Promise<CartView> {
    ensurePositiveQuantity(input.quantity);
    await this.assertStockAvailable(input.variantId, input.quantity);
    return this.toView(await this.carts.addItem(userId, input));
  }

  async changeQuantity(userId: string, variantId: string, quantity: number): Promise<CartView> {
    ensurePositiveQuantity(quantity);
    await this.assertStockAvailable(variantId, quantity);
    return this.toView(await this.carts.changeQuantity(userId, variantId, quantity));
  }

  private async assertStockAvailable(variantId: string, quantity: number): Promise<void> {
    const av = await this.inventory.checkAvailability(variantId, DEFAULT_STORE_ID, quantity);
    if (!av.available) {
      throw new InsufficientCartStockError(variantId, quantity, av.availableQuantity);
    }
  }

  async removeItem(userId: string, variantId: string): Promise<CartView> {
    return this.toView(await this.carts.removeItem(userId, variantId));
  }

  async applyPromoCode(userId: string, code: string): Promise<CartView> {
    const normalizedCode = code.trim().toUpperCase();
    if (!this.promotions) return this.toView(await this.carts.setPromoCode(userId, normalizedCode));
    await this.promotions.validateCode(normalizedCode);
    return this.toView(await this.carts.setPromoCode(userId, normalizedCode));
  }

  private async toView(
    cart: Awaited<ReturnType<CartRepository['getOrCreate']>>,
  ): Promise<CartView> {
    const items = await Promise.all(
      cart.items.map(async (item) => {
        const [pricing, availability] = await Promise.all([
          this.pricing
            .calculate({ variantId: item.variantId, quantity: item.quantity })
            .catch((error) => {
              if (error instanceof Error && error.name === 'PriceNotFoundError') return null;
              throw error;
            }),
          this.inventory.checkAvailability(item.variantId, DEFAULT_STORE_ID, item.quantity),
        ]);
        return {
          ...item,
          pricing,
          availability,
          available: pricing !== null && availability.available,
        };
      }),
    );

    const totalBeforeDiscountCents = items.reduce(
      (sum, item) => sum + (item.pricing?.totalCents ?? 0),
      0,
    );
    const discount =
      cart.promoCode && this.promotions
        ? await this.promotions.calculateDiscount(cart.promoCode, totalBeforeDiscountCents)
        : null;

    return {
      id: cart.id,
      userId: cart.userId,
      items,
      currency: 'EUR',
      netSubtotalCents: items.reduce((sum, item) => sum + (item.pricing?.netSubtotalCents ?? 0), 0),
      vatAmountCents: items.reduce((sum, item) => sum + (item.pricing?.vatAmountCents ?? 0), 0),
      discount,
      totalBeforeDiscountCents,
      totalCents: totalBeforeDiscountCents - (discount?.discountCents ?? 0),
      createdAt: cart.createdAt,
      updatedAt: cart.updatedAt,
    };
  }
}

function ensurePositiveQuantity(quantity: number): void {
  if (!Number.isInteger(quantity) || quantity <= 0) throw new InvalidCartQuantityError();
}
