// src/routes/user.routes.ts
import express, { Request, Response, NextFunction } from 'express';
import passport from './passport.routes';
import multer from 'multer';
import path from 'path';
import fs from 'node:fs/promises';
import * as bcrypt from 'bcrypt';
import zxcvbn from 'zxcvbn';
import { z } from 'zod';
import * as db from '../services/db/index.db';
import { logger } from '../services/logger.server';
import { UserProfile } from '../types';
import { userService } from '../services/userService';
import { ForeignKeyConstraintError } from '../services/db/errors.db';
import { validateRequest } from '../middleware/validation.middleware';

const router = express.Router();

/**
 * Validates the strength of a password using zxcvbn.
 * @param password The password to check.
 * @returns An object with `isValid` and an optional `feedback` message.
 */
const validatePasswordStrength = (password: string): { isValid: boolean; feedback?: string } => {
  const MIN_PASSWORD_SCORE = 3; // Require a 'Good' or 'Strong' password (score 3 or 4)
  const strength = zxcvbn(password);

  if (strength.score < MIN_PASSWORD_SCORE) {
    const feedbackMessage = strength.feedback.warning || (strength.feedback.suggestions && strength.feedback.suggestions[0]);
    return { isValid: false, feedback: `Password is too weak. ${feedbackMessage || 'Please choose a stronger password.'}`.trim() };
  }

  return { isValid: true };
};

// Helper for consistent required string validation (handles missing/null/empty)
const requiredString = (message: string) => z.preprocess((val) => val ?? '', z.string().min(1, message));

// --- Zod Schemas for User Routes (as per ADR-003) ---

const numericIdParam = (key: string) => z.object({
  params: z.object({ [key]: z.coerce.number().int().positive(`Invalid ID for parameter '${key}'. Must be a number.`) }),
});

const updateProfileSchema = z.object({
  body: z.object({ full_name: z.string().optional(), avatar_url: z.string().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().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 });
    }),
  }),
});

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: z.coerce.number().int().positive().optional().default(20),
    offset: z.coerce.number().int().nonnegative().optional().default(0),
  }),
});

// 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 }));

// --- Multer Configuration for Avatar Uploads ---

// Ensure the directory for avatar uploads exists.
const avatarUploadDir = path.join(process.cwd(), 'public', 'uploads', 'avatars');
fs.mkdir(avatarUploadDir, { recursive: true }).catch(err => {
  logger.error('Failed to create avatar upload directory:', err);
});

// Define multer storage configuration. The `req.user` object will be available
// here because the passport middleware runs before this route handler.
const avatarStorage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, avatarUploadDir),
  filename: (req, file, cb) => {
    const uniqueSuffix = `${(req.user as UserProfile).user.user_id}-${Date.now()}${path.extname(file.originalname)}`;
    cb(null, uniqueSuffix);
  },
});

const avatarUpload = multer({
  storage: avatarStorage,
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith('image/')) {
      cb(null, true);
    } else {
      // Reject the file with a specific error
      cb(new Error('Only image files are allowed!'));
    }
  },
});

/**
 * 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) => {
    try { // The try-catch block was already correct here.
      if (!req.file) return res.status(400).json({ message: 'No avatar file uploaded.' });
      const user = req.user as UserProfile;
      const avatarUrl = `/uploads/avatars/${req.file.filename}`;
      const updatedProfile = await db.userRepo.updateUserProfile(user.user_id, { avatar_url: avatarUrl }, req.log);
      res.json(updatedProfile);
    } catch (error) {
      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) => {
    // Fix: Cast to UserProfile and access the nested user property.
    const user = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    try {
      const { query } = req as unknown as GetNotificationsRequest;
      const limit = query.limit ? Number(query.limit) : 20;
      const offset = query.offset ? Number(query.offset) : 0;
      const notifications = await db.notificationRepo.getNotificationsForUser(user.user_id, limit, offset, req.log);
      res.json(notifications);
    } catch (error) {
      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 { // The try-catch block was already correct here.
      // Fix: Cast to UserProfile and access the nested user property.
      const user = req.user as UserProfile;
      await db.notificationRepo.markAllNotificationsAsRead(user.user_id, req.log);
      res.status(204).send(); // No Content
    } catch (error) {
      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 { // The try-catch block was already correct here.
      // Fix: Cast to UserProfile and access the nested user property.
      const user = req.user as UserProfile;
      // Apply ADR-003 pattern for type safety
      const { params } = req as unknown as MarkNotificationReadRequest;
      await db.notificationRepo.markNotificationAsRead(params.notificationId, user.user_id, req.log);
      res.status(204).send(); // Success, no content to return
    } catch (error) {
      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 user = req.user as UserProfile;
  try {
    logger.debug(`[ROUTE] Calling db.userRepo.findUserProfileById for user: ${user.user_id}`);
    const userProfile = await db.userRepo.findUserProfileById(user.user_id, req.log);
    res.json(userProfile);
  } 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 user = 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(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 user = req.user as UserProfile;
  // Apply ADR-003 pattern for type safety
  const { body } = req as unknown as UpdatePasswordRequest;

  try {
    const saltRounds = 10;
    const hashedPassword = await bcrypt.hash(body.newPassword, saltRounds);
    await db.userRepo.updateUserPassword(user.user_id, hashedPassword, 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 user = req.user as UserProfile;
  // Apply ADR-003 pattern for type safety
  const { body } = req as unknown as DeleteAccountRequest;

  try {
    const userWithHash = await db.userRepo.findUserWithPasswordHashById(user.user_id, req.log);
    if (!userWithHash || !userWithHash.password_hash) {
      return res.status(404).json({ message: 'User not found or password not set.' });
    }

    const isMatch = await bcrypt.compare(body.password, userWithHash.password_hash);
    if (!isMatch) {
      return res.status(403).json({ message: 'Incorrect password.' });
    }

    await db.userRepo.deleteUserById(user.user_id, 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 user = req.user as UserProfile;
  try {
    const items = await db.personalizationRepo.getWatchedItems(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 user = 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(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 });
    }
    const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
    logger.error({
      errorMessage,
      body: req.body,
    });
    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 user = req.user as UserProfile;
  // Apply ADR-003 pattern for type safety
  const { params } = req as unknown as DeleteWatchedItemRequest;
  try {
    await db.personalizationRepo.removeWatchedItem(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 user = req.user as UserProfile;
  try {
    const lists = await db.shoppingRepo.getShoppingLists(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 user = req.user as UserProfile;
  const { params } = req as unknown as GetShoppingListRequest;
  try {
    const list = await db.shoppingRepo.getShoppingListById(params.listId, 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 user = 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(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 });
    }
    const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
    logger.error({
      errorMessage,
      body: req.body,
    });
    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 user = 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, 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: requiredString('customItemName required?'),
  }).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 });
    }
    const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
    logger.error({
      errorMessage,
      params: req.params, body: req.body
    });
    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 user = 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(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 user = req.user as UserProfile;
  try {
    const restrictions = await db.personalizationRepo.getUserDietaryRestrictions(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 user = req.user as UserProfile;
  // Apply ADR-003 pattern for type safety
  const { body } = req as unknown as SetUserRestrictionsRequest;
  try {
    await db.personalizationRepo.setUserDietaryRestrictions(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 });
    }
    const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
    logger.error({
      errorMessage,
      body: req.body,
    });
    next(error);
  }
});

router.get('/me/appliances', validateRequest(emptySchema), async (req, res, next: NextFunction) => {
  logger.debug(`[ROUTE] GET /api/users/me/appliances - ENTER`);
  const user = req.user as UserProfile;
  try {
    const appliances = await db.personalizationRepo.getUserAppliances(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 user = req.user as UserProfile;
  // Apply ADR-003 pattern for type safety
  const { body } = req as unknown as SetUserAppliancesRequest;
  try {
    await db.personalizationRepo.setUserAppliances(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 });
    }
    const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
    logger.error({
      errorMessage,
      body: req.body,
    });
    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 user = req.user as UserProfile;
    // Apply ADR-003 pattern for type safety
    const { params } = req as unknown as GetAddressRequest;
    try {
      const addressId = params.addressId;
      // Security check: Ensure the requested addressId matches the one on the user's profile.
      if (user.address_id !== addressId) {
          return res.status(403).json({ message: 'Forbidden: You can only access your own address.' });
      }
      const address = await db.addressRepo.getAddressById(addressId, req.log); // This will throw NotFoundError if not found
      res.json(address);
    } catch (error) {
      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().optional(),
    address_line_2: z.string().optional(),
    city: z.string().optional(),
    province_state: z.string().optional(),
    postal_code: z.string().optional(),
    country: z.string().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 user = 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(user, addressData, req.log); // This was a duplicate, fixed.
        res.status(200).json({ message: 'Address updated successfully', address_id: addressId });
    } catch (error) {
        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 user = 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, user.user_id, false, req.log);
    res.status(204).send();
  } catch (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().optional(),
    description: z.string().optional(),
    instructions: z.string().optional(),
    prep_time_minutes: z.number().int().optional(),
    cook_time_minutes: z.number().int().optional(),
    servings: z.number().int().optional(),
    photo_url: z.string().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 user = 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, user.user_id, body, req.log);
        res.json(updatedRecipe);
    } catch (error) {
        next(error);
    }
});

// --- General Multer Error Handler ---
// This should be placed after all routes that use multer.
// It catches errors from `fileFilter` and other multer issues (e.g., file size limits).
router.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  if (err instanceof multer.MulterError) {
    // A Multer error occurred when uploading (e.g., file too large).
    return res.status(400).json({ message: `File upload error: ${err.message}` });
  } else if (err && err.message === 'Only image files are allowed!') {
    // A custom error from our fileFilter.
    return res.status(400).json({ message: err.message });
  }
  next(err); // Pass on to the next error handler if it's not a multer error we handle.
});

export default router;