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 { createCartService } from '../../cart/index.js';
import { createInventoryService } from '../../inventory/index.js';
import { createOrderService } from '../../orders/index.js';
import { createPricingService } from '../../pricing/index.js';
import { createShippingService } from '../../shipping/index.js';
import { CheckoutService } from '../application/checkout-service.js';
import { CheckoutError, type CheckoutResult } from '../domain/checkout.js';
import { InMemoryCheckoutMetrics } from '../infrastructure/metrics.js';
import { createNoOpTelemetry, type Tracer } from '../../observability/index.js';
import { StubPaymentProvider } from '../infrastructure/payment-provider.js';

export interface CheckoutRoutesDeps {
  pool: pg.Pool;
  authenticate: Authenticate;
  tracer?: Tracer;
}

const checkoutBodySchema = z
  .object({
    address: z.object({
      country: z.string().min(2).max(80),
      postalCode: z.string().min(1).max(20),
    }),
    promoCode: z.string().min(1).max(64).optional().nullable(),
    idempotencyKey: z.string().min(1).max(120),
  })
  .strip();

export async function registerCheckoutRoutes(
  app: FastifyInstance,
  deps: CheckoutRoutesDeps,
): Promise<void> {
  const pricing = createPricingService(deps.pool);
  const inventory = createInventoryService(deps.pool);
  const shipping = createShippingService(deps.pool);
  const orders = createOrderService(deps.pool);
  const cart = createCartService(deps.pool, pricing, inventory);
  const metrics = new InMemoryCheckoutMetrics();
  const tracer = deps.tracer ?? createNoOpTelemetry().tracer;
  const orderLookup = new PgIdempotencyLookup(deps.pool);

  const service = new CheckoutService({
    cart,
    pricing,
    inventory,
    shipping,
    orders,
    payments: new StubPaymentProvider(),
    orderLookup,
    metrics,
    tracer,
    getCartWeightKg: async (items) => {
      if (items.length === 0) return 0;
      const productIds = [...new Set(items.map((item) => item.productId))];
      const result = await deps.pool.query<{ id: string; unit_weight_kg: string | number }>(
        `SELECT id, unit_weight_kg FROM catalog_products WHERE id = ANY($1::uuid[])`,
        [productIds],
      );
      const weights = new Map(result.rows.map((row) => [row.id, Number(row.unit_weight_kg ?? 1)]));
      return items.reduce(
        (total, item) => total + (weights.get(item.productId) ?? 1) * item.quantity,
        0,
      );
    },
  });

  const checkoutSchema: FastifySchema = {
    tags: ['Checkout'],
    summary: 'Checkout',
    description: 'Procesa el carrito del usuario y crea el pedido.',
    body: {
      type: 'object',
      required: ['address', 'idempotencyKey'],
      properties: {
        address: {
          type: 'object',
          required: ['country', 'postalCode'],
          properties: {
            country: { type: 'string' },
            postalCode: { type: 'string' },
          },
        },
        promoCode: { type: 'string', maxLength: 64, nullable: true },
        idempotencyKey: {
          type: 'string',
          maxLength: 120,
          description: 'Idempotency key para evitar duplicados',
        },
      },
    },
    response: { 401: errorSchema },
  };
  app.post('/checkout', { schema: checkoutSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    const input = parseJson(checkoutBodySchema, request.body);
    try {
      const result = await service.execute({
        userId: user.id,
        address: input.address,
        promoCode: input.promoCode ?? null,
        idempotencyKey: input.idempotencyKey,
      });
      return reply.send(serializeResult(result));
    } catch (error) {
      throw mapCheckoutError(error);
    }
  });
}

class PgIdempotencyLookup {
  constructor(private readonly pool: pg.Pool) {}
  async findByIdempotency(
    userId: string,
    idempotencyKey: string,
  ): Promise<{ orderId: string } | undefined> {
    const result = await this.pool.query<{ id: string }>(
      'SELECT id FROM orders_orders WHERE user_id = $1 AND idempotency_key = $2',
      [userId, idempotencyKey],
    );
    const row = result.rows[0];
    return row ? { orderId: row.id } : undefined;
  }
}

function mapCheckoutError(error: unknown): Error {
  if (error instanceof CheckoutError)
    return new AppError(error.httpStatus, error.code, error.message);
  return error instanceof Error ? error : new Error('Unknown checkout error');
}

function serializeResult(result: CheckoutResult) {
  return {
    order: {
      id: result.order.id,
      state: result.order.state,
      totalCents: result.order.totalCents,
      items: result.order.items.map((item) => ({
        productId: item.productId,
        variantId: item.variantId,
        quantity: item.quantity,
        unitPriceCents: item.unitPriceCents,
        taxCents: item.taxCents,
      })),
    },
    paymentIntent: result.paymentIntent,
    reservedVariantIds: result.reservedVariantIds,
  };
}
