import type { FastifyInstance } from 'fastify';
import type { FastifySchema } from 'fastify';
import type pg from 'pg';
import { z } from 'zod';
import type { Authenticate } from '../../../shared/auth.js';
import { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import type { InventoryServicePort } from '../../inventory/index.js';
import type { PricingServicePort } from '../../pricing/index.js';
import type { PromotionServicePort } from '../../promotions/index.js';
import { CartService } from '../application/cart-service.js';
import type { CartItemView, CartView } from '../domain/cart.js';
import { InvalidCartQuantityError, InsufficientCartStockError } from '../domain/errors.js';
import { PgCartRepository } from '../infrastructure/pg-cart-repository.js';

export interface CartRoutesDeps {
  pool: pg.Pool;
  authenticate: Authenticate;
  pricing: PricingServicePort;
  inventory: InventoryServicePort;
  promotions?: PromotionServicePort;
}

const variantParamSchema = z.object({ variantId: z.uuid() });
const itemBodySchema = z
  .object({ productId: z.uuid(), variantId: z.uuid(), quantity: z.number().int().positive() })
  .strip();
const quantityBodySchema = z.object({ quantity: z.number().int().positive() }).strip();
const promoCodeBodySchema = z.object({ code: z.string().min(1).max(64) }).strip();

export async function registerCartRoutes(
  app: FastifyInstance,
  deps: CartRoutesDeps,
): Promise<void> {
  const service = new CartService(
    new PgCartRepository(deps.pool),
    deps.pricing,
    deps.inventory,
    deps.promotions,
  );

  const cartSchema: FastifySchema = {
    tags: ['Cart'],
    summary: 'Get cart',
    description: 'Devuelve el carrito del usuario autenticado.',
    response: { 401: errorSchema },
  };
  app.get('/cart', { schema: cartSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    return reply.send(serializeCart(await service.getCart(user.id)));
  });

  const addItemSchema: FastifySchema = {
    tags: ['Cart'],
    summary: 'Add item to cart',
    body: { type: 'object' },
    response: { 201: { type: 'object' }, 401: errorSchema },
  };
  app.post('/cart/items', { schema: addItemSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    const input = parseJson(itemBodySchema, request.body);
    try {
      return reply.code(201).send(serializeCart(await service.addItem(user.id, input)));
    } catch (error) {
      throw mapCartError(error);
    }
  });

  const updateItemSchema: FastifySchema = {
    tags: ['Cart'],
    summary: 'Change item quantity',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    body: {
      type: 'object',
      required: ['quantity'],
      properties: { quantity: { type: 'integer', minimum: 1 } },
    },
    response: { 401: errorSchema },
  };
  app.patch('/cart/items/:variantId', { schema: updateItemSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    const { variantId } = parseJson(variantParamSchema, request.params);
    const { quantity } = parseJson(quantityBodySchema, request.body);
    try {
      return reply.send(serializeCart(await service.changeQuantity(user.id, variantId, quantity)));
    } catch (error) {
      throw mapCartError(error);
    }
  });

  const removeItemSchema: FastifySchema = {
    tags: ['Cart'],
    summary: 'Remove item from cart',
    params: {
      type: 'object',
      required: ['variantId'],
      properties: { variantId: { type: 'string', format: 'uuid' } },
    },
    response: { 401: errorSchema },
  };
  app.delete('/cart/items/:variantId', { schema: removeItemSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    const { variantId } = parseJson(variantParamSchema, request.params);
    return reply.send(serializeCart(await service.removeItem(user.id, variantId)));
  });

  const promoSchema: FastifySchema = {
    tags: ['Cart'],
    summary: 'Apply promo code',
    body: {
      type: 'object',
      required: ['code'],
      properties: { code: { type: 'string', maxLength: 64 } },
    },
    response: { 401: errorSchema },
  };
  app.post('/cart/promo-code', { schema: promoSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    const { code } = parseJson(promoCodeBodySchema, request.body);
    try {
      return reply.send(serializeCart(await service.applyPromoCode(user.id, code)));
    } catch (error) {
      throw mapCartError(error);
    }
  });
}

function mapCartError(error: unknown): Error {
  if (error instanceof InsufficientCartStockError)
    return new AppError(
      409,
      'INSUFFICIENT_STOCK',
      `Only ${error.available} units available; you requested ${error.requested}.`,
    );
  if (error instanceof InvalidCartQuantityError)
    return new AppError(422, 'INVALID_CART_QUANTITY', error.message);
  if (error instanceof Error && error.name.includes('Promotion'))
    return new AppError(422, 'PROMOTION_INVALID', error.message);
  return error instanceof Error ? error : new Error('Unknown cart error');
}

function serializeCart(cart: CartView) {
  return {
    id: cart.id,
    userId: cart.userId,
    items: cart.items.map(serializeItem),
    currency: cart.currency,
    netSubtotalCents: cart.netSubtotalCents,
    vatAmountCents: cart.vatAmountCents,
    discount: cart.discount,
    totalBeforeDiscountCents: cart.totalBeforeDiscountCents,
    totalCents: cart.totalCents,
    createdAt: cart.createdAt.toISOString(),
    updatedAt: cart.updatedAt.toISOString(),
  };
}

function serializeItem(item: CartItemView) {
  return {
    id: item.id,
    productId: item.productId,
    variantId: item.variantId,
    quantity: item.quantity,
    available: item.available,
    availability: item.availability,
    pricing: item.pricing,
    createdAt: item.createdAt.toISOString(),
    updatedAt: item.updatedAt.toISOString(),
  };
}
