/**
 * Full-Text Search routes — search, index, suggestions.
 * @module search/routes
 */

import type { FastifyInstance } from 'fastify';
import { UnauthorizedError, ForbiddenError, BadRequestError } from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import { SearchQuerySchema, IndexEntitySchema, ReindexRequestSchema } from './types.js';
import * as repo from './repository.js';

function requireAuth(req: { jwtPayload?: { sub: string } }): string {
  if (!req.jwtPayload?.sub) throw new UnauthorizedError('Authentication required');
  return req.jwtPayload.sub;
}

function requireAdmin(req: { jwtPayload?: { sub: string; role?: string } }): string {
  const userId = requireAuth(req);
  if (req.jwtPayload?.role !== 'admin') throw new ForbiddenError('Admin access required');
  return userId;
}

export async function searchRoutes(app: FastifyInstance): Promise<void> {
  // ── Search (any auth user) ─────────────────────────────────
  app.get('/search', async req => {
    requireAuth(req);
    const productId = getRequestProductId(req);
    const raw = req.query as Record<string, unknown>;

    // Parse query params
    const parsed = SearchQuerySchema.safeParse({
      q: raw.q,
      entityTypes:
        typeof raw.entityTypes === 'string'
          ? raw.entityTypes.split(',').filter(Boolean)
          : undefined,
      tags: typeof raw.tags === 'string' ? raw.tags.split(',').filter(Boolean) : undefined,
      from: raw.from,
      to: raw.to,
      limit: raw.limit ? parseInt(String(raw.limit), 10) : undefined,
      offset: raw.offset ? parseInt(String(raw.offset), 10) : undefined,
    });

    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const { q, entityTypes, tags, from, to, limit, offset } = parsed.data;

    // Record search for suggestions (best-effort, don't block)
    repo.recordSearchQuery(productId, q).catch(() => {});

    const { results, total } = await repo.search(productId, q, {
      entityTypes,
      tags,
      from,
      to,
      limit,
      offset,
    });

    return { query: q, results, total, limit, offset };
  });

  // ── Suggestions (autocomplete) ─────────────────────────────
  app.get('/search/suggestions', async req => {
    requireAuth(req);
    const productId = getRequestProductId(req);
    const { q, limit: limitStr } = req.query as { q?: string; limit?: string };

    if (!q || q.length < 2) {
      return { suggestions: [] };
    }

    const parsedLimit = limitStr ? parseInt(limitStr, 10) : 10;
    const safeLimit =
      Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 50) : 10;

    const suggestions = await repo.getSuggestions(productId, q, safeLimit);
    return { suggestions: suggestions.map(s => s.text) };
  });

  // ── Index entity (admin or system) ─────────────────────────
  app.post('/search/index', async (req, reply) => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = IndexEntitySchema.parse(req.body);

    const now = new Date().toISOString();
    const doc = {
      id: `${input.entityType}:${input.entityId}`,
      productId,
      entityId: input.entityId,
      entityType: input.entityType,
      searchText: input.searchText,
      title: input.title,
      deepLink: input.deepLink,
      tags: input.tags,
      isActive: input.isActive,
      indexedAt: now,
      updatedAt: now,
    };

    const indexed = await repo.indexEntity(doc);
    req.log.info({ entityId: input.entityId, entityType: input.entityType }, 'Entity indexed');
    reply.status(201);
    return indexed;
  });

  // ── Remove from index ──────────────────────────────────────
  // id format is "entityType:entityId" (e.g. "user:user-123")
  app.delete<{ Params: { entityType: string; entityId: string } }>(
    '/search/index/:entityType/:entityId',
    async (req, reply) => {
      requireAdmin(req);
      const productId = getRequestProductId(req);
      const { entityType, entityId } = req.params;
      const docId = `${entityType}:${entityId}`;

      const removed = await repo.removeFromIndex(docId, productId);
      if (!removed) {
        throw new BadRequestError('Entity not found in index');
      }

      req.log.info({ entityId, entityType }, 'Entity removed from index');
      reply.status(204);
      return;
    }
  );

  // ── Index stats ────────────────────────────────────────────
  app.get('/search/stats', async req => {
    requireAdmin(req);
    const productId = getRequestProductId(req);
    return repo.getIndexStats(productId);
  });

  // ── Reindex request (admin trigger) ────────────────────────
  app.post('/search/reindex', async req => {
    const userId = requireAdmin(req);
    const productId = getRequestProductId(req);
    const input = ReindexRequestSchema.parse(req.body);

    // In production, this would enqueue an async job.
    // For MVP, return acknowledgment.
    req.log.info({ productId, userId, entityTypes: input.entityTypes }, 'Reindex requested');

    return {
      status: 'accepted',
      entityTypes: input.entityTypes,
      message: 'Reindex job queued. Check /search/stats for progress.',
    };
  });
}
