import type { FastifyInstance } from 'fastify';
import type { FastifySchema } from 'fastify';
import type pg from 'pg';
import { z } from 'zod';
import { requireRole, type Authenticate } from '../../../shared/auth.js';
import { parseJson } from '../../../shared/http-input.js';
import { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { PromotionServiceImpl } from '../application/promotion-service.js';
import type { Promotion } from '../domain/promotion.js';
import { PgPromotionRepository } from '../infrastructure/pg-promotion-repository.js';

export interface PromotionsRoutesDeps {
  pool: pg.Pool;
  authenticate: Authenticate;
}

const promotionBodySchema = z.object({
  code: z.string().min(1).max(64),
  type: z.enum(['percent', 'fixed_amount']),
  value: z.number().int().positive(),
  startsAt: z.coerce.date(),
  endsAt: z.coerce.date(),
  usageLimit: z.number().int().positive().optional().nullable(),
  active: z.boolean().optional(),
});

const promotionPatchSchema = promotionBodySchema.partial().omit({ code: true });

export async function registerPromotionsRoutes(
  app: FastifyInstance,
  deps: PromotionsRoutesDeps,
): Promise<void> {
  const service = new PromotionServiceImpl(new PgPromotionRepository(deps.pool));

  // List all promotions (admin)
  const listPromoSchema: FastifySchema = {
    tags: ['Promotions'],
    summary: 'List promotions (admin)',
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.get('/promotions', { schema: listPromoSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const items = await service.list();
    return reply.send({ items: items.map(serializePromotion) });
  });

  // Create promotion (admin)
  const createPromoSchema: FastifySchema = {
    tags: ['Promotions'],
    summary: 'Create promotion (admin)',
    body: { type: 'object' },
    response: { 201: { type: 'object' }, 401: errorSchema, 403: errorSchema },
  };
  app.post('/promotions', { schema: createPromoSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const input = parseJson(promotionBodySchema, request.body);
    const promotion = await service.create(input);
    return reply.code(201).send(serializePromotion(promotion));
  });

  // Update promotion (admin)
  const patchPromoSchema: FastifySchema = {
    tags: ['Promotions'],
    summary: 'Update promotion (admin)',
    params: { type: 'object', required: ['code'], properties: { code: { type: 'string' } } },
    body: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
  };
  app.patch('/promotions/:code', { schema: patchPromoSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { code } = parseJson(z.object({ code: z.string() }), request.params);
    const patch = parseJson(promotionPatchSchema, request.body);
    try {
      const promotion = await service.update(code, patch);
      return reply.send(serializePromotion(promotion));
    } catch (error) {
      throw error instanceof Error && error.message === 'Promotion not found'
        ? new AppError(404, 'NOT_FOUND', 'Promotion not found')
        : error instanceof Error
          ? error
          : new Error('Unknown error');
    }
  });

  // Delete promotion (admin)
  const deletePromoSchema: FastifySchema = {
    tags: ['Promotions'],
    summary: 'Delete promotion (admin)',
    params: { type: 'object', required: ['code'], properties: { code: { type: 'string' } } },
    response: { 204: { type: 'null' }, 401: errorSchema, 403: errorSchema, 404: errorSchema },
  };
  app.delete('/promotions/:code', { schema: deletePromoSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { code } = parseJson(z.object({ code: z.string() }), request.params);
    try {
      await service.delete(code);
    } catch {
      throw new AppError(404, 'NOT_FOUND', 'Promotion not found');
    }
    return reply.code(204).send();
  });
}

function serializePromotion(promotion: Promotion) {
  return {
    code: promotion.code,
    type: promotion.type,
    value: promotion.value,
    startsAt: promotion.startsAt.toISOString(),
    endsAt: promotion.endsAt.toISOString(),
    usageLimit: promotion.usageLimit,
    usageCount: promotion.usageCount,
    active: promotion.active,
    createdAt: promotion.createdAt.toISOString(),
    updatedAt: promotion.updatedAt.toISOString(),
  };
}
