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 { CmsService } from '../application/cms-service.js';
import { DuplicateSlugError, PageNotFoundError, PageNotPublishedError } from '../domain/errors.js';
import type { Page } from '../domain/page.js';
import { PgCmsRepository } from '../infrastructure/pg-cms-repository.js';

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

const createSchema = z
  .object({
    slug: z
      .string()
      .min(1)
      .max(160)
      .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'slug must be kebab-case'),
    title: z.string().min(1).max(200),
    body: z.string().min(1).max(50000),
  })
  .strip();

const updateSchema = z
  .object({
    slug: z
      .string()
      .min(1)
      .max(160)
      .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
      .optional(),
    title: z.string().min(1).max(200).optional(),
    body: z.string().min(1).max(50000).optional(),
  })
  .strip();

const idParamSchema = z.object({ id: z.uuid() });
const slugParamSchema = z.object({ slug: z.string().min(1).max(160) });

export async function registerCmsRoutes(app: FastifyInstance, deps: CmsRoutesDeps): Promise<void> {
  const service = new CmsService(new PgCmsRepository(deps.pool));

  const listPagesSchema: FastifySchema = {
    tags: ['CMS'],
    summary: 'List CMS pages (admin)',
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.get('/cms/pages', { schema: listPagesSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const pages = await service.listAll();
    return reply.send({ items: pages.map(serialize) });
  });

  const createPageSchema: FastifySchema = {
    tags: ['CMS'],
    summary: 'Create CMS page (admin)',
    body: { type: 'object' },
    response: { 201: { type: 'object' }, 401: errorSchema, 403: errorSchema },
  };
  app.post('/cms/pages', { schema: createPageSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const input = parseJson(createSchema, request.body);
    try {
      const page = await service.create(input);
      return reply.code(201).send(serialize(page));
    } catch (error) {
      throw mapCmsError(error);
    }
  });

  const updatePageSchema: FastifySchema = {
    tags: ['CMS'],
    summary: 'Update CMS page (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    body: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.patch('/cms/pages/:id', { schema: updatePageSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = idParamSchema.parse(request.params);
    const patch = parseJson(updateSchema, request.body);
    try {
      const page = await service.update(id, patch);
      return reply.send(serialize(page));
    } catch (error) {
      throw mapCmsError(error);
    }
  });

  const publishSchema: FastifySchema = {
    tags: ['CMS'],
    summary: 'Publish page (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post('/cms/pages/:id/publish', { schema: publishSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = idParamSchema.parse(request.params);
    try {
      const page = await service.publish(id);
      return reply.send(serialize(page));
    } catch (error) {
      throw mapCmsError(error);
    }
  });

  const unpublishSchema: FastifySchema = {
    tags: ['CMS'],
    summary: 'Unpublish page (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post('/cms/pages/:id/unpublish', { schema: unpublishSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = idParamSchema.parse(request.params);
    try {
      const page = await service.unpublish(id);
      return reply.send(serialize(page));
    } catch (error) {
      throw mapCmsError(error);
    }
  });

  const getPageSchema: FastifySchema = {
    tags: ['CMS'],
    summary: 'Get published page (público)',
    params: { type: 'object', required: ['slug'], properties: { slug: { type: 'string' } } },
    response: { 404: errorSchema },
  };
  app.get('/cms/pages/:slug', { schema: getPageSchema }, async (request, reply) => {
    const { slug } = slugParamSchema.parse(request.params);
    try {
      const page = await service.getPublicPageBySlug(slug);
      return reply.send(serialize(page));
    } catch (error) {
      throw mapCmsError(error);
    }
  });
}

function mapCmsError(error: unknown): Error {
  if (error instanceof DuplicateSlugError)
    return new AppError(409, 'CMS_DUPLICATE_SLUG', error.message);
  if (error instanceof PageNotPublishedError)
    return new AppError(404, 'CMS_NOT_FOUND', error.message);
  if (error instanceof PageNotFoundError) return new AppError(404, 'CMS_NOT_FOUND', error.message);
  return error instanceof Error ? error : new Error('Unknown cms error');
}

function serialize(page: Page) {
  return {
    id: page.id,
    slug: page.slug,
    title: page.title,
    body: page.body,
    status: page.status,
    createdAt: page.createdAt.toISOString(),
    updatedAt: page.updatedAt.toISOString(),
  };
}
