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 { requireRole } from '../../../shared/auth.js';
import { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import { ReviewsService } from '../application/reviews-service.js';
import {
  DuplicateReviewError,
  InvalidReviewError,
  ReviewNotEligibleError,
  ReviewNotFoundError,
} from '../domain/errors.js';
import { PgOrderItemVerifier } from '../infrastructure/pg-order-item-verifier.js';
import { PgReviewsRepository } from '../infrastructure/pg-reviews-repository.js';

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

const submitSchema = z
  .object({
    productId: z.uuid(),
    orderItemId: z.uuid(),
    rating: z.number().int().min(1).max(5),
    title: z.string().min(1).max(200),
    body: z.string().min(1).max(5000),
  })
  .strip();

const moderateSchema = z.object({ status: z.enum(['published', 'rejected']) });
const moderateParamSchema = z.object({ id: z.uuid() });
const productIdQuerySchema = z.object({ productId: z.uuid() });
const adminListSchema = z.object({
  status: z.enum(['pending', 'published', 'rejected']).optional(),
  limit: z.coerce.number().int().positive().max(100).optional().default(20),
  offset: z.coerce.number().int().min(0).optional().default(0),
});

export async function registerReviewsRoutes(
  app: FastifyInstance,
  deps: ReviewsRoutesDeps,
): Promise<void> {
  const repository = new PgReviewsRepository(deps.pool);
  const verifier = new PgOrderItemVerifier(deps.pool);
  const service = new ReviewsService(repository, verifier);

  const submitReviewSchema: FastifySchema = {
    tags: ['Reviews'],
    summary: 'Submit review',
    description: 'Crea una reseña. Requiere compra verificada.',
    body: { type: 'object' },
    response: { 201: { type: 'object' }, 401: errorSchema },
  };
  app.post('/reviews', { schema: submitReviewSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    const input = parseJson(submitSchema, request.body);
    try {
      const review = await service.submit({ ...input, userId: user.id });
      return reply.code(201).send(serializeReview(review));
    } catch (error) {
      throw mapError(error);
    }
  });

  const moderateSwaggerSchema: FastifySchema = {
    tags: ['Reviews'],
    summary: 'Moderate review (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    body: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.patch('/reviews/:id/moderate', { schema: moderateSwaggerSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = moderateParamSchema.parse(request.params);
    const { status } = moderateSchema.parse(request.body);
    try {
      const review = await service.moderate(id, status);
      return reply.send(serializeReview(review));
    } catch (error) {
      throw mapError(error);
    }
  });

  const listReviewsSchema: FastifySchema = {
    tags: ['Reviews'],
    summary: 'List reviews (público)',
    querystring: {
      type: 'object',
      required: ['productId'],
      properties: { productId: { type: 'string', format: 'uuid' } },
    },
  };
  app.get('/reviews', { schema: listReviewsSchema }, async (request, reply) => {
    const { productId } = productIdQuerySchema.parse(request.query);
    const result = await service.listPublishedByProduct(productId);
    return reply.send(result);
  });

  const adminReviewsSchema: FastifySchema = {
    tags: ['Reviews'],
    summary: 'List reviews (admin)',
    querystring: {
      type: 'object',
      properties: {
        status: { type: 'string', enum: ['pending', 'published', 'rejected'] },
        limit: { type: 'integer', default: 20 },
        offset: { type: 'integer', default: 0 },
      },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.get('/reviews/admin', { schema: adminReviewsSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { status, limit, offset } = adminListSchema.parse(request.query ?? {});
    const result = await service.listAll({ status, limit, offset });
    return reply.send(result);
  });
}

function mapError(error: unknown): Error {
  if (error instanceof DuplicateReviewError)
    return new AppError(409, 'REVIEW_DUPLICATE', error.message);
  if (error instanceof ReviewNotEligibleError)
    return new AppError(403, 'REVIEW_NOT_ELIGIBLE', error.message);
  if (error instanceof ReviewNotFoundError)
    return new AppError(404, 'REVIEW_NOT_FOUND', error.message);
  if (error instanceof InvalidReviewError)
    return new AppError(422, 'INVALID_REVIEW', error.message);
  return error instanceof Error ? error : new Error('Unknown review error');
}

function serializeReview(review: ReturnType<typeof Object>) {
  return {
    id: (review as { id: string }).id,
    productId: (review as { productId: string }).productId,
    userId: (review as { userId: string }).userId,
    orderId: (review as { orderId: string }).orderId,
    orderItemId: (review as { orderItemId: string }).orderItemId,
    rating: (review as { rating: number }).rating,
    title: (review as { title: string }).title,
    body: (review as { body: string }).body,
    status: (review as { status: string }).status,
    createdAt: (review as { createdAt: Date }).createdAt.toISOString(),
    updatedAt: (review as { updatedAt: Date }).updatedAt.toISOString(),
  };
}
