/**
 * Public roadmap REST endpoints — no authentication required.
 *
 * GET    /public/roadmap           — list public items (visible on roadmap)
 * GET    /public/roadmap/stats     — aggregate counts for public items
 * POST   /public/submit            — submit a feature request / bug (requires email)
 * POST   /public/items/:id/vote    — toggle upvote by email
 * GET    /public/items/:id         — get single public item detail
 */

import type { FastifyInstance } from 'fastify';
import rateLimit from '@fastify/rate-limit';
import { getRequestProductId } from '../../lib/request-context.js';
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
import * as itemRepo from '../items/repository.js';
import * as voteRepo from '../votes/repository.js';
import { PRIORITY_ORDER, type TrackerItemDoc } from '../items/types.js';
import { PublicSubmitSchema, PublicVoteSchema, PublicRoadmapQuerySchema } from './types.js';

export async function publicRoutes(app: FastifyInstance) {
  // Rate limiting for all public routes — generous default (60/min per IP)
  await app.register(rateLimit, {
    max: 60,
    timeWindow: '1 minute',
    keyGenerator: req => req.ip,
  });

  // Public roadmap list — only visibility=public, excludes closed/wont_fix
  app.get('/public/roadmap', async req => {
    const raw = req.query as Record<string, string>;
    const parsed = PublicRoadmapQuerySchema.safeParse(raw);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const query = parsed.data;

    return itemRepo.list({
      productId: query.productId || getRequestProductId(req),
      visibility: 'public',
      type: query.type,
      status: query.status,
      q: query.q,
      sortBy: query.sortBy,
      sortOrder: query.sortOrder,
      limit: query.limit,
      offset: query.offset,
    });
  });

  // Public roadmap stats
  app.get('/public/roadmap/stats', async req => {
    const { productId } = req.query as { productId?: string };
    const pid = productId || getRequestProductId(req);

    const { items } = await itemRepo.list({
      productId: pid,
      visibility: 'public',
      limit: 1000,
      offset: 0,
      sortBy: 'createdAt',
      sortOrder: 'desc',
    });

    const byStatus: Record<string, number> = {};
    const byType: Record<string, number> = {};
    let totalVotes = 0;

    for (const item of items) {
      byStatus[item.status] = (byStatus[item.status] || 0) + 1;
      byType[item.type] = (byType[item.type] || 0) + 1;
      totalVotes += item.voteCount;
    }

    return { total: items.length, byStatus, byType, totalVotes };
  });

  // Get single public item detail
  app.get('/public/items/:id', async req => {
    const { id } = req.params as { id: string };
    const item = await itemRepo.getById(id);
    if (!item || item.visibility !== 'public') {
      throw new NotFoundError('Item not found');
    }
    return item;
  });

  // Public submission — stricter rate limit: 10/min per IP
  app.post(
    '/public/submit',
    { config: { rateLimit: { max: 10, timeWindow: '1 minute' } } },
    async (req, reply) => {
      const parsed = PublicSubmitSchema.safeParse(req.body);
      if (!parsed.success) {
        throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
      }
      const input = parsed.data;
      const pid = input.productId || getRequestProductId(req);
      const now = new Date().toISOString();

      const doc: TrackerItemDoc = {
        id: `trk_${crypto.randomUUID()}`,
        productId: pid,
        type: input.type,
        status: 'open',
        priority: input.priority,
        title: input.title,
        description: input.description,
        labels: [],
        assignee: null,
        reportedBy: `${input.name} <${input.email}>`,
        source: 'user_submitted',
        visibility: 'internal',
        voteCount: 1,
        commentCount: 0,
        priorityOrder: PRIORITY_ORDER[input.priority] ?? 2,
        targetRelease: null,
        createdAt: now,
        updatedAt: now,
      };

      const created = await itemRepo.create(doc);

      // Auto-vote for the submitter
      await voteRepo.create({
        id: `vote_${crypto.randomUUID()}`,
        itemId: created.id,
        productId: pid,
        userId: `email:${input.email}`,
        createdAt: now,
      });

      reply.code(201);
      return { id: created.id, title: created.title, status: created.status };
    }
  );

  // Public vote (by email) — moderate rate limit: 30/min per IP
  app.post(
    '/public/items/:id/vote',
    { config: { rateLimit: { max: 30, timeWindow: '1 minute' } } },
    async req => {
      const { id } = req.params as { id: string };
      const parsed = PublicVoteSchema.safeParse(req.body);
      if (!parsed.success) {
        throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
      }

      const item = await itemRepo.getById(id);
      if (!item || item.visibility !== 'public') {
        throw new NotFoundError('Item not found');
      }

      const voterId = `email:${parsed.data.email}`;
      const existing = await voteRepo.getByItemAndUser(id, voterId);

      if (existing) {
        await voteRepo.remove(existing.id);
        const newCount = await voteRepo.countByItem(id);
        await itemRepo.updateVoteCount(id, newCount);
        return { voted: false, voteCount: newCount };
      } else {
        await voteRepo.create({
          id: `vote_${crypto.randomUUID()}`,
          itemId: id,
          productId: item.productId,
          userId: voterId,
          createdAt: new Date().toISOString(),
        });
        const newCount = await voteRepo.countByItem(id);
        await itemRepo.updateVoteCount(id, newCount);
        return { voted: true, voteCount: newCount };
      }
    }
  );
}
