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 { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import {
  CreateBrand,
  GetBrandBySlug,
  ListBrands,
  UpdateBrand,
} from '../application/brand-use-cases.js';
import type { Brand } from '../domain/brand.js';
import { BrandSlugAlreadyExistsError } from '../domain/errors.js';
import { PgBrandRepository } from '../infrastructure/pg-brand-repository.js';

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

const slugSchema = z
  .string()
  .min(1)
  .max(160)
  .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
const slugParamSchema = z.object({ slug: slugSchema });
const idParamSchema = z.object({ id: z.uuid() });

const newBrandSchema = z.object({
  name: z.string().min(1).max(200),
  slug: slugSchema,
  seoTitle: z.string().min(1).max(200).optional().nullable(),
  seoDescription: z.string().min(1).max(500).optional().nullable(),
});

const brandPatchSchema = newBrandSchema
  .partial()
  .refine((value) => Object.values(value).some((field) => field !== undefined), {
    message: 'At least one brand field is required',
  });

export async function registerBrandsRoutes(
  app: FastifyInstance,
  deps: BrandsRoutesDeps,
): Promise<void> {
  const repository = new PgBrandRepository(deps.pool);
  const getBySlug = new GetBrandBySlug(repository);
  const listBrands = new ListBrands(repository);
  const createBrand = new CreateBrand(repository);
  const updateBrand = new UpdateBrand(repository);

  const listBrandsSchema: FastifySchema = {
    tags: ['Brands'],
    summary: 'List brands (público)',
  };
  app.get('/brands', { schema: listBrandsSchema }, async (_request, reply) => {
    const items = await listBrands.execute();
    return reply.send({ items: items.map(serializeBrand) });
  });

  const publicBrandSchema: FastifySchema = {
    tags: ['Brands'],
    summary: 'Get brand by slug (público)',
    params: { type: 'object', required: ['slug'], properties: { slug: { type: 'string' } } },
    response: { 404: errorSchema },
  };
  app.get('/marca/:slug', { schema: publicBrandSchema }, async (request, reply) => {
    const { slug } = parseJson(slugParamSchema, request.params);
    const brand = await getBySlug.execute(slug);
    if (!brand) {
      throw new AppError(404, 'NOT_FOUND', 'Brand not found');
    }
    return reply.send(serializeBrand(brand));
  });

  const createBrandSchema: FastifySchema = {
    tags: ['Brands'],
    summary: 'Create brand (admin)',
    body: { type: 'object' },
    response: { 201: { type: 'object' }, 401: errorSchema, 403: errorSchema },
  };
  app.post('/brands', { schema: createBrandSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const input = parseJson(newBrandSchema, request.body);
    try {
      const brand = await createBrand.execute(input);
      return reply.code(201).send(serializeBrand(brand));
    } catch (error) {
      throw mapBrandError(error);
    }
  });

  const updateBrandSchema: FastifySchema = {
    tags: ['Brands'],
    summary: 'Update brand (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    body: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
  };
  app.patch('/brands/:id', { schema: updateBrandSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = parseJson(idParamSchema, request.params);
    const patch = parseJson(brandPatchSchema, request.body);
    try {
      const brand = await updateBrand.execute(id, patch);
      if (!brand) {
        throw new AppError(404, 'NOT_FOUND', 'Brand not found');
      }
      return reply.send(serializeBrand(brand));
    } catch (error) {
      throw mapBrandError(error);
    }
  });

  // DELETE /brands/:id
  const deleteBrandSchema: FastifySchema = {
    tags: ['Brands'],
    summary: 'Delete brand (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    response: { 204: { type: 'null' }, 401: errorSchema, 403: errorSchema },
  };
  app.delete('/brands/:id', { schema: deleteBrandSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = parseJson(idParamSchema, request.params);
    try {
      await repository.delete(id);
    } catch {
      throw new AppError(404, 'NOT_FOUND', 'Brand not found');
    }
    return reply.code(204).send();
  });
}

function mapBrandError(error: unknown): Error {
  if (error instanceof BrandSlugAlreadyExistsError) {
    return new AppError(409, 'BRAND_SLUG_EXISTS', error.message);
  }
  return error instanceof Error ? error : new Error('Unknown brand error');
}

function serializeBrand(brand: Brand) {
  return {
    id: brand.id,
    name: brand.name,
    slug: brand.slug,
    url: `/marca/${brand.slug}`,
    seoTitle: brand.seoTitle,
    seoDescription: brand.seoDescription,
    createdAt: brand.createdAt.toISOString(),
    updatedAt: brand.updatedAt.toISOString(),
  };
}
