// src/routes/user.routes.ts
import express, { Request, Response, NextFunction } from 'express';
import passport from './passport.routes';
import multer from 'multer'; // Keep for MulterError type check
import { z } from 'zod';
import { logger } from '../services/logger.server';
import { UserProfile } from '../types';
import {
  createUploadMiddleware,
  handleMulterError,
} from '../middleware/multer.middleware';
import { userService } from '../services/userService';
import { ForeignKeyConstraintError } from '../services/db/errors.db';
import { validateRequest } from '../middleware/validation.middleware';
import { validatePasswordStrength } from '../utils/authUtils';
import {
  requiredString,
  numericIdParam,
  optionalNumeric,
  optionalBoolean,
} from '../utils/zodUtils';
import * as db from '../services/db/index.db';
import { cleanupUploadedFile } from '../utils/fileUtils';

const router = express.Router();

const updateProfileSchema = z.object({
  body: z
    .object({
      full_name: z.string().optional(),
      avatar_url: z.preprocess(
        (val) => (val === '' ? undefined : val),
        z.string().trim().url().optional(),
      ),
    })
    .refine((data) => Object.keys(data).length > 0, {
      message: 'At least one field to update must be provided.',
    }),
});

const updatePasswordSchema = z.object({
  body: z.object({
    newPassword: z
      .string()
      .trim() // Trim whitespace from password input.
      .min(8, 'Password must be at least 8 characters long.')
      .superRefine((password, ctx) => {
        const strength = validatePasswordStrength(password);
        if (!strength.isValid) ctx.addIssue({ code: 'custom', message: strength.feedback });
      }),
  }),
});

// The `requiredString` utility (modified in `zodUtils.ts`) now handles trimming,
// so no changes are needed here, but we are confirming that password trimming
// is now implicitly handled for this schema.
const deleteAccountSchema = z.object({
  body: z.object({ password: requiredString("Field 'password' is required.") }),
});

const addWatchedItemSchema = z.object({
  body: z.object({
    itemName: requiredString("Field 'itemName' is required."),
    category: requiredString("Field 'category' is required."),
  }),
});

const createShoppingListSchema = z.object({
  body: z.object({ name: requiredString("Field 'name' is required.") }),
});

// Apply the JWT authentication middleware to all routes in this file.
const notificationQuerySchema = z.object({
  query: z.object({
    limit: optionalNumeric({ default: 20, integer: true, positive: true }),
    offset: optionalNumeric({ default: 0, integer: true, nonnegative: true }),
    includeRead: optionalBoolean({ default: false }),
  }),
});

// An empty schema for routes that do not expect any input, to maintain a consistent validation pattern.
const emptySchema = z.object({});

// Any request to a /api/users/* endpoint will now require a valid JWT.
router.use(passport.authenticate('jwt', { session: false }));

const avatarUpload = createUploadMiddleware({
  storageType: 'avatar',
  fileSize: 1 * 1024 * 1024, // 1MB
  fileFilter: 'image',
});

/**
 * POST /api/users/profile/avatar - Upload a new avatar for the authenticated user.
 */
router.post(
  '/profile/avatar',
  avatarUpload.single('avatar'),
  async (req: Request, res: Response, next: NextFunction) => {
    // The try-catch block was already correct here.
    try {
      // The `requireFileUpload` middleware is not used here, so we must check for `req.file`.
      if (!req.file) return res.status(400).json({ message: 'No avatar file uploaded.' });
      const userProfile = req.user as UserProfile;
      const updatedProfile = await userService.updateUserAvatar(userProfile.user.user_id, req.file, req.log);
      res.json(updatedProfile);
    } catch (error) {
      // If an error occurs after the file has been uploaded (e.g., DB error),
      // we must clean up the orphaned file from the disk.
      await cleanupUploadedFile(req.file);
      logger.error({ error }, 'Error uploading avatar');
      next(error);
    }
  },
);

/**
 * GET /api/users/notifications - Get notifications for the authenticated user.
 * Supports pagination with `limit` and `offset` query parameters.
 */
type GetNotificationsRequest = z.infer<typeof notificationQuerySchema>;
router.get(
  '/notifications',
  validateRequest(notificationQuerySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    // Cast to UserProfile to access user properties safely.
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    try {
      const { query } = req as unknown as GetNotificationsRequest;
      const parsedQuery = notificationQuerySchema.parse({ query: req.query }).query;
      const notifications = await db.notificationRepo.getNotificationsForUser(
        userProfile.user.user_id,
        parsedQuery.limit!,
        parsedQuery.offset!,
        parsedQuery.includeRead!,
        req.log,
      );
      res.json(notifications);
    } catch (error) {
      logger.error({ error }, 'Error fetching notifications');
      next(error);
    }
  },
);

/**
 * POST /api/users/notifications/mark-all-read - Mark all of the user's notifications as read.
 */
router.post(
  '/notifications/mark-all-read',
  validateRequest(emptySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const userProfile = req.user as UserProfile;
      await db.notificationRepo.markAllNotificationsAsRead(userProfile.user.user_id, req.log);
      res.status(204).send(); // No Content
    } catch (error) {
      logger.error({ error }, 'Error marking all notifications as read');
      next(error);
    }
  },
);

/**
 * POST /api/users/notifications/:notificationId/mark-read - Mark a single notification as read.
 */
const notificationIdSchema = numericIdParam('notificationId');
type MarkNotificationReadRequest = z.infer<typeof notificationIdSchema>;
router.post(
  '/notifications/:notificationId/mark-read',
  validateRequest(notificationIdSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const userProfile = req.user as UserProfile;
      // Apply ADR-003 pattern for type safety
      const { params } = req as unknown as MarkNotificationReadRequest;
      await db.notificationRepo.markNotificationAsRead(
        params.notificationId,
        userProfile.user.user_id,
        req.log,
      );
      res.status(204).send(); // Success, no content to return
    } catch (error) {
      logger.error({ error }, 'Error marking notification as read');
      next(error);
    }
  },
);

/**
 * GET /api/users/profile - Get the full profile for the authenticated user.
 */
router.get('/profile', validateRequest(emptySchema), async (req, res, next: NextFunction) => {
  logger.debug(`[ROUTE] GET /api/users/profile - ENTER`);
  const userProfile = req.user as UserProfile;
  try {
    logger.debug(
      `[ROUTE] Calling db.userRepo.findUserProfileById for user: ${userProfile.user.user_id}`,
    );
    const fullUserProfile = await db.userRepo.findUserProfileById(
      userProfile.user.user_id,
      req.log,
    );
    res.json(fullUserProfile);
  } catch (error) {
    logger.error({ error }, `[ROUTE] GET /api/users/profile - ERROR`);
    next(error);
  }
});

/**
 * PUT /api/users/profile - Update the user's profile information.
 */
type UpdateProfileRequest = z.infer<typeof updateProfileSchema>;
router.put(
  '/profile',
  validateRequest(updateProfileSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/profile - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as UpdateProfileRequest;
    try {
      const updatedProfile = await db.userRepo.updateUserProfile(
        userProfile.user.user_id,
        body,
        req.log,
      );
      res.json(updatedProfile);
    } catch (error) {
      logger.error({ error }, `[ROUTE] PUT /api/users/profile - ERROR`);
      next(error);
    }
  },
);

/**
 * PUT /api/users/profile/password - Update the user's password.
 */
type UpdatePasswordRequest = z.infer<typeof updatePasswordSchema>;
router.put(
  '/profile/password',
  validateRequest(updatePasswordSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/profile/password - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as UpdatePasswordRequest;

    try {
      await userService.updateUserPassword(userProfile.user.user_id, body.newPassword, req.log);
      res.status(200).json({ message: 'Password updated successfully.' });
    } catch (error) {
      logger.error({ error }, `[ROUTE] PUT /api/users/profile/password - ERROR`);
      next(error);
    }
  },
);

/**
 * DELETE /api/users/account - Delete the user's own account.
 */
type DeleteAccountRequest = z.infer<typeof deleteAccountSchema>;
router.delete(
  '/account',
  validateRequest(deleteAccountSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] DELETE /api/users/account - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as DeleteAccountRequest;

    try {
      await userService.deleteUserAccount(userProfile.user.user_id, body.password, req.log);
      res.status(200).json({ message: 'Account deleted successfully.' });
    } catch (error) {
      logger.error({ error }, `[ROUTE] DELETE /api/users/account - ERROR`);
      next(error);
    }
  },
);

/**
 * GET /api/users/watched-items - Get all watched items for the authenticated user.
 */
router.get('/watched-items', validateRequest(emptySchema), async (req, res, next: NextFunction) => {
  logger.debug(`[ROUTE] GET /api/users/watched-items - ENTER`);
  const userProfile = req.user as UserProfile;
  try {
    const items = await db.personalizationRepo.getWatchedItems(userProfile.user.user_id, req.log);
    res.json(items);
  } catch (error) {
    logger.error({ error }, `[ROUTE] GET /api/users/watched-items - ERROR`);
    next(error);
  }
});

/**
 * POST /api/users/watched-items - Add a new item to the user's watchlist.
 */
type AddWatchedItemRequest = z.infer<typeof addWatchedItemSchema>;
router.post(
  '/watched-items',
  validateRequest(addWatchedItemSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] POST /api/users/watched-items - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as AddWatchedItemRequest;
    try {
      const newItem = await db.personalizationRepo.addWatchedItem(
        userProfile.user.user_id,
        body.itemName,
        body.category,
        req.log,
      );
      res.status(201).json(newItem);
    } catch (error) {
      if (error instanceof ForeignKeyConstraintError) {
        return res.status(400).json({ message: error.message });
      }
      logger.error({ error, body: req.body }, 'Failed to add watched item');
      next(error);
    }
  },
);

/**
 * DELETE /api/users/watched-items/:masterItemId - Remove an item from the watchlist.
 */
const watchedItemIdSchema = numericIdParam('masterItemId');
type DeleteWatchedItemRequest = z.infer<typeof watchedItemIdSchema>;
router.delete(
  '/watched-items/:masterItemId',
  validateRequest(watchedItemIdSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] DELETE /api/users/watched-items/:masterItemId - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { params } = req as unknown as DeleteWatchedItemRequest;
    try {
      await db.personalizationRepo.removeWatchedItem(
        userProfile.user.user_id,
        params.masterItemId,
        req.log,
      );
      res.status(204).send();
    } catch (error) {
      logger.error({ error }, `[ROUTE] DELETE /api/users/watched-items/:masterItemId - ERROR`);
      next(error);
    }
  },
);

/**
 * GET /api/users/shopping-lists - Get all shopping lists for the user.
 */
router.get(
  '/shopping-lists',
  validateRequest(emptySchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] GET /api/users/shopping-lists - ENTER`);
    const userProfile = req.user as UserProfile;
    try {
      const lists = await db.shoppingRepo.getShoppingLists(userProfile.user.user_id, req.log);
      res.json(lists);
    } catch (error) {
      logger.error({ error }, `[ROUTE] GET /api/users/shopping-lists - ERROR`);
      next(error);
    }
  },
);

/**
 * GET /api/users/shopping-lists/:listId - Get a single shopping list by its ID.
 */
const shoppingListIdSchema = numericIdParam('listId');
type GetShoppingListRequest = z.infer<typeof shoppingListIdSchema>;
router.get(
  '/shopping-lists/:listId',
  validateRequest(shoppingListIdSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] GET /api/users/shopping-lists/:listId - ENTER`);
    const userProfile = req.user as UserProfile;
    const { params } = req as unknown as GetShoppingListRequest;
    try {
      const list = await db.shoppingRepo.getShoppingListById(
        params.listId,
        userProfile.user.user_id,
        req.log,
      );
      res.json(list);
    } catch (error) {
      logger.error(
        { error, listId: params.listId },
        `[ROUTE] GET /api/users/shopping-lists/:listId - ERROR`,
      );
      next(error);
    }
  },
);

/**
 * POST /api/users/shopping-lists - Create a new shopping list.
 */
type CreateShoppingListRequest = z.infer<typeof createShoppingListSchema>;
router.post(
  '/shopping-lists',
  validateRequest(createShoppingListSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] POST /api/users/shopping-lists - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as CreateShoppingListRequest;
    try {
      const newList = await db.shoppingRepo.createShoppingList(
        userProfile.user.user_id,
        body.name,
        req.log,
      );
      res.status(201).json(newList);
    } catch (error) {
      if (error instanceof ForeignKeyConstraintError) {
        return res.status(400).json({ message: error.message });
      }
      logger.error({ error, body: req.body }, 'Failed to create shopping list');
      next(error);
    }
  },
);

/**
 * DELETE /api/users/shopping-lists/:listId - Delete a shopping list.
 */
router.delete(
  '/shopping-lists/:listId',
  validateRequest(shoppingListIdSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] DELETE /api/users/shopping-lists/:listId - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { params } = req as unknown as GetShoppingListRequest;
    try {
      await db.shoppingRepo.deleteShoppingList(params.listId, userProfile.user.user_id, req.log);
      res.status(204).send();
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
      logger.error(
        { errorMessage, params: req.params },
        `[ROUTE] DELETE /api/users/shopping-lists/:listId - ERROR`,
      );
      next(error);
    }
  },
);

/**
 * POST /api/users/shopping-lists/:listId/items - Add an item to a shopping list.
 */
const addShoppingListItemSchema = shoppingListIdSchema.extend({
  body: z
    .object({
      masterItemId: z.number().int().positive().optional(),
      customItemName: z
        .string()
        .trim()
        .min(1, 'customItemName cannot be empty if provided')
        .optional(),
    })
    .refine((data) => data.masterItemId || data.customItemName, {
      message: 'Either masterItemId or customItemName must be provided.',
    }),
});
type AddShoppingListItemRequest = z.infer<typeof addShoppingListItemSchema>;
router.post(
  '/shopping-lists/:listId/items',
  validateRequest(addShoppingListItemSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] POST /api/users/shopping-lists/:listId/items - ENTER`);
    // Apply ADR-003 pattern for type safety
    const { params, body } = req as unknown as AddShoppingListItemRequest;
    try {
      const newItem = await db.shoppingRepo.addShoppingListItem(params.listId, body, req.log);
      res.status(201).json(newItem);
    } catch (error) {
      if (error instanceof ForeignKeyConstraintError) {
        return res.status(400).json({ message: error.message });
      }
      logger.error({ error, params: req.params, body: req.body }, 'Failed to add shopping list item');
      next(error);
    }
  },
);

/**
 * PUT /api/users/shopping-lists/items/:itemId - Update a shopping list item.
 */
const updateShoppingListItemSchema = numericIdParam('itemId').extend({
  body: z
    .object({
      quantity: z.number().int().nonnegative().optional(),
      is_purchased: z.boolean().optional(),
    })
    .refine((data) => Object.keys(data).length > 0, {
      message: 'At least one field (quantity, is_purchased) must be provided.',
    }),
});
type UpdateShoppingListItemRequest = z.infer<typeof updateShoppingListItemSchema>;
router.put(
  '/shopping-lists/items/:itemId',
  validateRequest(updateShoppingListItemSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/shopping-lists/items/:itemId - ENTER`);
    // Apply ADR-003 pattern for type safety
    const { params, body } = req as unknown as UpdateShoppingListItemRequest;
    try {
      const updatedItem = await db.shoppingRepo.updateShoppingListItem(
        params.itemId,
        body,
        req.log,
      );
      res.json(updatedItem);
    } catch (error: unknown) {
      logger.error(
        { error, params: req.params, body: req.body },
        `[ROUTE] PUT /api/users/shopping-lists/items/:itemId - ERROR`,
      );
      next(error);
    }
  },
);

/**
 * DELETE /api/users/shopping-lists/items/:itemId - Remove an item from a shopping list.
 */
const shoppingListItemIdSchema = numericIdParam('itemId');
type DeleteShoppingListItemRequest = z.infer<typeof shoppingListItemIdSchema>;
router.delete(
  '/shopping-lists/items/:itemId',
  validateRequest(shoppingListItemIdSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] DELETE /api/users/shopping-lists/items/:itemId - ENTER`);
    // Apply ADR-003 pattern for type safety
    const { params } = req as unknown as DeleteShoppingListItemRequest;
    try {
      await db.shoppingRepo.removeShoppingListItem(params.itemId, req.log);
      res.status(204).send();
    } catch (error: unknown) {
      logger.error(
        { error, params: req.params },
        `[ROUTE] DELETE /api/users/shopping-lists/items/:itemId - ERROR`,
      );
      next(error);
    }
  },
);

/**
 * PUT /api/users/profile/preferences - Update user preferences.
 */
const updatePreferencesSchema = z.object({
  body: z.object({}).passthrough(), // Ensures body is an object, allows any properties
});
type UpdatePreferencesRequest = z.infer<typeof updatePreferencesSchema>;
router.put(
  '/profile/preferences',
  validateRequest(updatePreferencesSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/profile/preferences - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as UpdatePreferencesRequest;
    try {
      const updatedProfile = await db.userRepo.updateUserPreferences(
        userProfile.user.user_id,
        body,
        req.log,
      );
      res.json(updatedProfile);
    } catch (error) {
      logger.error({ error }, `[ROUTE] PUT /api/users/profile/preferences - ERROR`);
      next(error);
    }
  },
);

router.get(
  '/me/dietary-restrictions',
  validateRequest(emptySchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] GET /api/users/me/dietary-restrictions - ENTER`);
    const userProfile = req.user as UserProfile;
    try {
      const restrictions = await db.personalizationRepo.getUserDietaryRestrictions(
        userProfile.user.user_id,
        req.log,
      );
      res.json(restrictions);
    } catch (error) {
      logger.error({ error }, `[ROUTE] GET /api/users/me/dietary-restrictions - ERROR`);
      next(error);
    }
  },
);

const setUserRestrictionsSchema = z.object({
  body: z.object({ restrictionIds: z.array(z.number().int().positive()) }),
});
type SetUserRestrictionsRequest = z.infer<typeof setUserRestrictionsSchema>;
router.put(
  '/me/dietary-restrictions',
  validateRequest(setUserRestrictionsSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/me/dietary-restrictions - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as SetUserRestrictionsRequest;
    try {
      await db.personalizationRepo.setUserDietaryRestrictions(
        userProfile.user.user_id,
        body.restrictionIds,
        req.log,
      );
      res.status(204).send();
    } catch (error) {
      if (error instanceof ForeignKeyConstraintError) {
        return res.status(400).json({ message: error.message });
      }
      logger.error({ error, body: req.body }, 'Failed to set user dietary restrictions');
      next(error);
    }
  },
);

router.get('/me/appliances', validateRequest(emptySchema), async (req, res, next: NextFunction) => {
  logger.debug(`[ROUTE] GET /api/users/me/appliances - ENTER`);
  const userProfile = req.user as UserProfile;
  try {
    const appliances = await db.personalizationRepo.getUserAppliances(
      userProfile.user.user_id,
      req.log,
    );
    res.json(appliances);
  } catch (error) {
    logger.error({ error }, `[ROUTE] GET /api/users/me/appliances - ERROR`);
    next(error);
  }
});

const setUserAppliancesSchema = z.object({
  body: z.object({ applianceIds: z.array(z.number().int().positive()) }),
});
type SetUserAppliancesRequest = z.infer<typeof setUserAppliancesSchema>;
router.put(
  '/me/appliances',
  validateRequest(setUserAppliancesSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/me/appliances - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body } = req as unknown as SetUserAppliancesRequest;
    try {
      await db.personalizationRepo.setUserAppliances(
        userProfile.user.user_id,
        body.applianceIds,
        req.log,
      );
      res.status(204).send();
    } catch (error) {
      if (error instanceof ForeignKeyConstraintError) {
        return res.status(400).json({ message: error.message });
      }
      logger.error({ error, body: req.body }, 'Failed to set user appliances');
      next(error);
    }
  },
);

/**
 * GET /api/users/addresses/:addressId - Get a specific address by its ID.
 * This is protected to ensure a user can only fetch their own address details.
 */
const addressIdSchema = numericIdParam('addressId');
type GetAddressRequest = z.infer<typeof addressIdSchema>;
router.get(
  '/addresses/:addressId',
  validateRequest(addressIdSchema),
  async (req, res, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { params } = req as unknown as GetAddressRequest;
    try {
      const addressId = params.addressId;
      const address = await userService.getUserAddress(userProfile, addressId, req.log);
      res.json(address);
    } catch (error) {
      logger.error({ error }, 'Error fetching user address');
      next(error);
    }
  },
);

/**
 * PUT /api/users/profile/address - Create or update the user's primary address.
 */
const updateUserAddressSchema = z.object({
  body: z
    .object({
      address_line_1: z.string().trim().optional(),
      address_line_2: z.string().trim().optional(),
      city: z.string().trim().optional(),
      province_state: z.string().trim().optional(),
      postal_code: z.string().trim().optional(),
      country: z.string().trim().optional(),
    })
    .refine((data) => Object.keys(data).length > 0, {
      message: 'At least one address field must be provided.',
    }),
});
type UpdateUserAddressRequest = z.infer<typeof updateUserAddressSchema>;
router.put(
  '/profile/address',
  validateRequest(updateUserAddressSchema),
  async (req, res, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { body: addressData } = req as unknown as UpdateUserAddressRequest;

    try {
      // Per ADR-002, complex operations involving multiple database writes should be
      // encapsulated in a single service method that manages the transaction.
      // This ensures both the address upsert and the user profile update are atomic.
      const addressId = await userService.upsertUserAddress(userProfile, addressData, req.log); // This was a duplicate, fixed.
      res.status(200).json({ message: 'Address updated successfully', address_id: addressId });
    } catch (error) {
      logger.error({ error }, 'Error updating user address');
      next(error);
    }
  },
);

/**
 * DELETE /api/users/recipes/:recipeId - Delete a recipe created by the user.
 */
const recipeIdSchema = numericIdParam('recipeId');
type DeleteRecipeRequest = z.infer<typeof recipeIdSchema>;
router.delete(
  '/recipes/:recipeId',
  validateRequest(recipeIdSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] DELETE /api/users/recipes/:recipeId - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { params } = req as unknown as DeleteRecipeRequest;
    try {
      await db.recipeRepo.deleteRecipe(params.recipeId, userProfile.user.user_id, false, req.log);
      res.status(204).send();
    } catch (error) {
      logger.error(
        { error, params: req.params },
        `[ROUTE] DELETE /api/users/recipes/:recipeId - ERROR`,
      );
      next(error);
    }
  },
);

/**
 * PUT /api/users/recipes/:recipeId - Update a recipe created by the user.
 */
const updateRecipeSchema = recipeIdSchema.extend({
  body: z
    .object({
      name: z.string().trim().optional(),
      description: z.string().trim().optional(),
      instructions: z.string().trim().optional(),
      prep_time_minutes: z.number().int().optional(),
      cook_time_minutes: z.number().int().optional(),
      servings: z.number().int().optional(),
      photo_url: z.string().trim().url().optional(),
    })
    .refine((data) => Object.keys(data).length > 0, { message: 'No fields provided to update.' }),
});
type UpdateRecipeRequest = z.infer<typeof updateRecipeSchema>;
router.put(
  '/recipes/:recipeId',
  validateRequest(updateRecipeSchema),
  async (req, res, next: NextFunction) => {
    logger.debug(`[ROUTE] PUT /api/users/recipes/:recipeId - ENTER`);
    const userProfile = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { params, body } = req as unknown as UpdateRecipeRequest;

    try {
      const updatedRecipe = await db.recipeRepo.updateRecipe(
        params.recipeId,
        userProfile.user.user_id,
        body,
        req.log,
      );
      res.json(updatedRecipe);
    } catch (error) {
      logger.error(
        { error, params: req.params, body: req.body },
        `[ROUTE] PUT /api/users/recipes/:recipeId - ERROR`,
      );
      next(error);
    }
  },
);

/* Catches errors from multer (e.g., file size, file filter) */
router.use(handleMulterError);

export default router;
