// src/routes/recipe.routes.ts
import { Router } from 'express';
import { z } from 'zod';
import * as db from '../services/db/index.db';
import { aiService } from '../services/aiService.server';
import passport from '../config/passport';
import { validateRequest } from '../middleware/validation.middleware';
import { requiredString, numericIdParam, optionalNumeric } from '../utils/zodUtils';
import { publicReadLimiter, suggestionLimiter, userUpdateLimiter } from '../config/rateLimiters';
import { sendSuccess, sendError, ErrorCode } from '../utils/apiResponse';
import type { UserProfile } from '../types';

const router = Router();

const bySalePercentageSchema = z.object({
  query: z.object({
    minPercentage: optionalNumeric({ default: 50, min: 0, max: 100 }),
  }),
});

const bySaleIngredientsSchema = z.object({
  query: z.object({
    minIngredients: optionalNumeric({ default: 3, integer: true, positive: true }),
  }),
});

const byIngredientAndTagSchema = z.object({
  query: z.object({
    ingredient: requiredString('Query parameter "ingredient" is required.'),
    tag: requiredString('Query parameter "tag" is required.'),
  }),
});

const recipeIdParamsSchema = numericIdParam('recipeId');

const suggestRecipeSchema = z.object({
  body: z.object({
    ingredients: z.array(z.string().min(1)).nonempty('At least one ingredient is required.'),
  }),
});

const addCommentSchema = recipeIdParamsSchema.extend({
  body: z.object({
    content: requiredString('Comment content is required.'),
    parentCommentId: z.number().int().positive().optional(),
  }),
});

/**
 * @openapi
 * /recipes/by-sale-percentage:
 *   get:
 *     tags: [Recipes]
 *     summary: Get recipes by sale percentage
 *     description: Get recipes based on the percentage of their ingredients currently on sale.
 *     parameters:
 *       - in: query
 *         name: minPercentage
 *         schema:
 *           type: number
 *           minimum: 0
 *           maximum: 100
 *           default: 50
 *         description: Minimum percentage of ingredients on sale
 *     responses:
 *       200:
 *         description: List of recipes matching criteria
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 */
router.get(
  '/by-sale-percentage',
  publicReadLimiter,
  validateRequest(bySalePercentageSchema),
  async (req, res, next) => {
    try {
      // Explicitly parse req.query to apply coercion (string -> number) and default values
      const { query } = bySalePercentageSchema.parse({ query: req.query });
      const recipes = await db.recipeRepo.getRecipesBySalePercentage(query.minPercentage!, req.log);
      sendSuccess(res, recipes);
    } catch (error) {
      req.log.error({ error }, 'Error fetching recipes in /api/recipes/by-sale-percentage:');
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/by-sale-ingredients:
 *   get:
 *     tags: [Recipes]
 *     summary: Get recipes by sale ingredients count
 *     description: Get recipes with at least a specified number of ingredients currently on sale.
 *     parameters:
 *       - in: query
 *         name: minIngredients
 *         schema:
 *           type: integer
 *           minimum: 1
 *           default: 3
 *         description: Minimum number of sale ingredients required
 *     responses:
 *       200:
 *         description: List of recipes matching criteria
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 */
router.get(
  '/by-sale-ingredients',
  publicReadLimiter,
  validateRequest(bySaleIngredientsSchema),
  async (req, res, next) => {
    try {
      // Explicitly parse req.query to apply coercion (string -> number) and default values
      const { query } = bySaleIngredientsSchema.parse({ query: req.query });
      const recipes = await db.recipeRepo.getRecipesByMinSaleIngredients(
        query.minIngredients!,
        req.log,
      );
      sendSuccess(res, recipes);
    } catch (error) {
      req.log.error({ error }, 'Error fetching recipes in /api/recipes/by-sale-ingredients:');
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/by-ingredient-and-tag:
 *   get:
 *     tags: [Recipes]
 *     summary: Find recipes by ingredient and tag
 *     description: Find recipes that contain a specific ingredient and have a specific tag.
 *     parameters:
 *       - in: query
 *         name: ingredient
 *         required: true
 *         schema:
 *           type: string
 *         description: Ingredient name to search for
 *       - in: query
 *         name: tag
 *         required: true
 *         schema:
 *           type: string
 *         description: Tag to filter by
 *     responses:
 *       200:
 *         description: List of matching recipes
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Missing required query parameters
 */
router.get(
  '/by-ingredient-and-tag',
  publicReadLimiter,
  validateRequest(byIngredientAndTagSchema),
  async (req, res, next) => {
    try {
      const { query } = byIngredientAndTagSchema.parse({ query: req.query });
      const recipes = await db.recipeRepo.findRecipesByIngredientAndTag(
        query.ingredient,
        query.tag,
        req.log,
      );
      sendSuccess(res, recipes);
    } catch (error) {
      req.log.error({ error }, 'Error fetching recipes in /api/recipes/by-ingredient-and-tag:');
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/{recipeId}/comments:
 *   get:
 *     tags: [Recipes]
 *     summary: Get recipe comments
 *     description: Get all comments for a specific recipe.
 *     parameters:
 *       - in: path
 *         name: recipeId
 *         required: true
 *         schema:
 *           type: integer
 *         description: Recipe ID
 *     responses:
 *       200:
 *         description: List of comments
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       404:
 *         description: Recipe not found
 */
router.get(
  '/:recipeId/comments',
  publicReadLimiter,
  validateRequest(recipeIdParamsSchema),
  async (req, res, next) => {
    try {
      // Explicitly parse req.params to coerce recipeId to a number
      const { params } = recipeIdParamsSchema.parse({ params: req.params });
      const comments = await db.recipeRepo.getRecipeComments(params.recipeId, req.log);
      sendSuccess(res, comments);
    } catch (error) {
      req.log.error({ error }, `Error fetching comments for recipe ID ${req.params.recipeId}:`);
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/{recipeId}:
 *   get:
 *     tags: [Recipes]
 *     summary: Get recipe by ID
 *     description: Get a single recipe by its ID, including ingredients and tags.
 *     parameters:
 *       - in: path
 *         name: recipeId
 *         required: true
 *         schema:
 *           type: integer
 *         description: Recipe ID
 *     responses:
 *       200:
 *         description: Recipe details with ingredients and tags
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       404:
 *         description: Recipe not found
 */
router.get(
  '/:recipeId',
  publicReadLimiter,
  validateRequest(recipeIdParamsSchema),
  async (req, res, next) => {
    try {
      // Explicitly parse req.params to coerce recipeId to a number
      const { params } = recipeIdParamsSchema.parse({ params: req.params });
      const recipe = await db.recipeRepo.getRecipeById(params.recipeId, req.log);
      sendSuccess(res, recipe);
    } catch (error) {
      req.log.error({ error }, `Error fetching recipe ID ${req.params.recipeId}:`);
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/suggest:
 *   post:
 *     tags: [Recipes]
 *     summary: Get AI recipe suggestion
 *     description: Generate a recipe suggestion based on provided ingredients using AI.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - ingredients
 *             properties:
 *               ingredients:
 *                 type: array
 *                 items:
 *                   type: string
 *                 minItems: 1
 *                 description: List of ingredients to use
 *     responses:
 *       200:
 *         description: AI-generated recipe suggestion
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       503:
 *         description: AI service unavailable
 */
router.post(
  '/suggest',
  suggestionLimiter,
  passport.authenticate('jwt', { session: false }),
  validateRequest(suggestRecipeSchema),
  async (req, res, next) => {
    try {
      const { body } = req as unknown as z.infer<typeof suggestRecipeSchema>;
      const suggestion = await aiService.generateRecipeSuggestion(body.ingredients, req.log);

      if (!suggestion) {
        return sendError(
          res,
          ErrorCode.SERVICE_UNAVAILABLE,
          'AI service is currently unavailable or failed to generate a suggestion.',
          503,
        );
      }

      sendSuccess(res, { suggestion });
    } catch (error) {
      req.log.error({ error }, 'Error generating recipe suggestion');
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/{recipeId}/comments:
 *   post:
 *     tags: [Recipes]
 *     summary: Add comment to recipe
 *     description: Add a comment to a recipe. Supports nested replies via parentCommentId.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: recipeId
 *         required: true
 *         schema:
 *           type: integer
 *         description: Recipe ID
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - content
 *             properties:
 *               content:
 *                 type: string
 *                 description: Comment content
 *               parentCommentId:
 *                 type: integer
 *                 description: Parent comment ID for replies (optional)
 *     responses:
 *       201:
 *         description: Comment added
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       404:
 *         description: Recipe or parent comment not found
 */
router.post(
  '/:recipeId/comments',
  userUpdateLimiter,
  passport.authenticate('jwt', { session: false }),
  validateRequest(addCommentSchema),
  async (req, res, next) => {
    try {
      const userProfile = req.user as UserProfile;
      const { params, body } = addCommentSchema.parse({ params: req.params, body: req.body });

      const comment = await db.recipeRepo.addRecipeComment(
        params.recipeId,
        userProfile.user.user_id,
        body.content,
        req.log,
        body.parentCommentId,
      );

      sendSuccess(res, comment, 201);
    } catch (error) {
      req.log.error({ error }, `Error adding comment to recipe ID ${req.params.recipeId}:`);
      next(error);
    }
  },
);

/**
 * @openapi
 * /recipes/{recipeId}/fork:
 *   post:
 *     tags: [Recipes]
 *     summary: Fork recipe
 *     description: Create a personal copy of a recipe that you can modify.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: recipeId
 *         required: true
 *         schema:
 *           type: integer
 *         description: Recipe ID to fork
 *     responses:
 *       201:
 *         description: Recipe forked successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       404:
 *         description: Recipe not found
 */
router.post(
  '/:recipeId/fork',
  userUpdateLimiter,
  passport.authenticate('jwt', { session: false }),
  validateRequest(recipeIdParamsSchema),
  async (req, res, next) => {
    try {
      const userProfile = req.user as UserProfile;
      const { params } = recipeIdParamsSchema.parse({ params: req.params });

      const forkedRecipe = await db.recipeRepo.forkRecipe(
        userProfile.user.user_id,
        params.recipeId,
        req.log,
      );

      sendSuccess(res, forkedRecipe, 201);
    } catch (error) {
      req.log.error({ error }, `Error forking recipe ID ${req.params.recipeId}:`);
      next(error);
    }
  },
);

export default router;
