// src/routes/inventory.routes.ts
/**
 * @file Inventory and Expiry Tracking API Routes
 * Provides endpoints for managing pantry inventory, expiry tracking, and alerts.
 */
import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import passport from '../config/passport';
import type { UserProfile } from '../types';
import { validateRequest } from '../middleware/validation.middleware';
import { numericIdParam, optionalNumeric } from '../utils/zodUtils';
import { sendSuccess, sendNoContent } from '../utils/apiResponse';
import * as expiryService from '../services/expiryService.server';

const router = express.Router();

// --- Zod Schemas for Inventory Routes ---

/**
 * Storage location validation
 */
const storageLocationSchema = z.enum(['fridge', 'freezer', 'pantry', 'room_temp']);

/**
 * Inventory source validation
 */
const inventorySourceSchema = z.enum(['manual', 'receipt_scan', 'upc_scan']);

/**
 * Alert method validation
 */
const alertMethodSchema = z.enum(['email', 'push', 'in_app']);

/**
 * Schema for inventory item ID parameter
 */
const inventoryIdParamSchema = numericIdParam(
  'inventoryId',
  "Invalid ID for parameter 'inventoryId'. Must be a number.",
);

/**
 * Schema for adding an inventory item
 */
const addInventoryItemSchema = z.object({
  body: z.object({
    product_id: z.number().int().positive().optional(),
    master_item_id: z.number().int().positive().optional(),
    item_name: z.string().min(1, 'Item name is required.').max(255),
    quantity: z.number().positive().default(1),
    unit: z.string().max(50).optional(),
    purchase_date: z.string().date('Purchase date must be in YYYY-MM-DD format.').optional(),
    expiry_date: z.string().date('Expiry date must be in YYYY-MM-DD format.').optional(),
    source: inventorySourceSchema,
    location: storageLocationSchema.optional(),
    notes: z.string().max(500).optional(),
  }),
});

/**
 * Schema for updating an inventory item
 */
const updateInventoryItemSchema = inventoryIdParamSchema.extend({
  body: z
    .object({
      quantity: z.number().positive().optional(),
      unit: z.string().max(50).optional(),
      expiry_date: z.string().date('Expiry date must be in YYYY-MM-DD format.').optional(),
      location: storageLocationSchema.optional(),
      notes: z.string().max(500).optional(),
      is_consumed: z.boolean().optional(),
    })
    .refine((data) => Object.keys(data).length > 0, {
      message: 'At least one field to update must be provided.',
    }),
});

/**
 * Schema for inventory query
 */
const inventoryQuerySchema = z.object({
  query: z.object({
    limit: optionalNumeric({ default: 50, min: 1, max: 100, integer: true }),
    offset: optionalNumeric({ default: 0, min: 0, integer: true }),
    location: storageLocationSchema.optional(),
    is_consumed: z
      .string()
      .optional()
      .transform((val) => (val === 'true' ? true : val === 'false' ? false : undefined)),
    expiring_within_days: z
      .string()
      .optional()
      .transform((val) => (val ? parseInt(val, 10) : undefined))
      .pipe(z.number().int().positive().optional()),
    category_id: z
      .string()
      .optional()
      .transform((val) => (val ? parseInt(val, 10) : undefined))
      .pipe(z.number().int().positive().optional()),
    search: z.string().max(100).optional(),
    sort_by: z.enum(['expiry_date', 'purchase_date', 'item_name', 'created_at']).optional(),
    sort_order: z.enum(['asc', 'desc']).optional(),
  }),
});

/**
 * Schema for alert settings update
 */
const updateAlertSettingsSchema = z.object({
  params: z.object({
    alertMethod: alertMethodSchema,
  }),
  body: z.object({
    days_before_expiry: z.number().int().min(1).max(30).optional(),
    is_enabled: z.boolean().optional(),
  }),
});

/**
 * Schema for days ahead parameter
 */
const daysAheadQuerySchema = z.object({
  query: z.object({
    days: z
      .string()
      .optional()
      .default('7')
      .transform((val) => parseInt(val, 10))
      .pipe(z.number().int().min(1).max(90)),
  }),
});

// Middleware to ensure user is authenticated for all inventory routes
router.use(passport.authenticate('jwt', { session: false }));

// ============================================================================
// INVENTORY ITEM ENDPOINTS
// ============================================================================

/**
 * @openapi
 * /inventory:
 *   get:
 *     tags: [Inventory]
 *     summary: Get inventory items
 *     description: Retrieve the user's pantry inventory with optional filtering and pagination.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 100
 *           default: 50
 *       - in: query
 *         name: offset
 *         schema:
 *           type: integer
 *           minimum: 0
 *           default: 0
 *       - in: query
 *         name: location
 *         schema:
 *           type: string
 *           enum: [fridge, freezer, pantry, room_temp]
 *       - in: query
 *         name: is_consumed
 *         schema:
 *           type: boolean
 *       - in: query
 *         name: expiring_within_days
 *         schema:
 *           type: integer
 *           minimum: 1
 *       - in: query
 *         name: category_id
 *         schema:
 *           type: integer
 *       - in: query
 *         name: search
 *         schema:
 *           type: string
 *           maxLength: 100
 *       - in: query
 *         name: sort_by
 *         schema:
 *           type: string
 *           enum: [expiry_date, purchase_date, item_name, created_at]
 *       - in: query
 *         name: sort_order
 *         schema:
 *           type: string
 *           enum: [asc, desc]
 *     responses:
 *       200:
 *         description: Inventory items retrieved
 *       401:
 *         description: Unauthorized
 */
router.get(
  '/',
  validateRequest(inventoryQuerySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type InventoryQueryRequest = z.infer<typeof inventoryQuerySchema>;
    const { query } = req as unknown as InventoryQueryRequest;

    try {
      const result = await expiryService.getInventory(
        {
          user_id: userProfile.user.user_id,
          location: query.location,
          is_consumed: query.is_consumed,
          expiring_within_days: query.expiring_within_days,
          category_id: query.category_id,
          search: query.search,
          limit: query.limit,
          offset: query.offset,
          sort_by: query.sort_by,
          sort_order: query.sort_order,
        },
        req.log,
      );
      sendSuccess(res, result);
    } catch (error) {
      req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching inventory');
      next(error);
    }
  },
);

/**
 * @openapi
 * /inventory:
 *   post:
 *     tags: [Inventory]
 *     summary: Add inventory item
 *     description: Add a new item to the user's pantry inventory.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - item_name
 *               - source
 *             properties:
 *               product_id:
 *                 type: integer
 *               master_item_id:
 *                 type: integer
 *               item_name:
 *                 type: string
 *                 maxLength: 255
 *               quantity:
 *                 type: number
 *                 minimum: 0
 *                 default: 1
 *               unit:
 *                 type: string
 *                 maxLength: 50
 *               purchase_date:
 *                 type: string
 *                 format: date
 *               expiry_date:
 *                 type: string
 *                 format: date
 *               source:
 *                 type: string
 *                 enum: [manual, receipt_scan, upc_scan]
 *               location:
 *                 type: string
 *                 enum: [fridge, freezer, pantry, room_temp]
 *               notes:
 *                 type: string
 *                 maxLength: 500
 *     responses:
 *       201:
 *         description: Item added to inventory
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized
 */
router.post(
  '/',
  validateRequest(addInventoryItemSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type AddItemRequest = z.infer<typeof addInventoryItemSchema>;
    const { body } = req as unknown as AddItemRequest;

    try {
      req.log.info(
        { userId: userProfile.user.user_id, itemName: body.item_name },
        'Adding item to inventory',
      );

      const item = await expiryService.addInventoryItem(userProfile.user.user_id, body, req.log);
      sendSuccess(res, item, 201);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, body },
        'Error adding inventory item',
      );
      next(error);
    }
  },
);

// ============================================================================
// EXPIRING ITEMS ENDPOINTS
// NOTE: These routes MUST be defined BEFORE /:inventoryId to avoid path conflicts
// ============================================================================

/**
 * @openapi
 * /inventory/expiring/summary:
 *   get:
 *     tags: [Inventory]
 *     summary: Get expiring items summary
 *     description: Get items grouped by expiry urgency (today, this week, this month, expired).
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Expiring items grouped by urgency
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 expiring_today:
 *                   type: array
 *                 expiring_this_week:
 *                   type: array
 *                 expiring_this_month:
 *                   type: array
 *                 already_expired:
 *                   type: array
 *                 counts:
 *                   type: object
 *                   properties:
 *                     today:
 *                       type: integer
 *                     this_week:
 *                       type: integer
 *                     this_month:
 *                       type: integer
 *                     expired:
 *                       type: integer
 *                     total:
 *                       type: integer
 *       401:
 *         description: Unauthorized
 */
router.get('/expiring/summary', async (req: Request, res: Response, next: NextFunction) => {
  const userProfile = req.user as UserProfile;

  try {
    const result = await expiryService.getExpiringItemsGrouped(userProfile.user.user_id, req.log);
    sendSuccess(res, result);
  } catch (error) {
    req.log.error(
      { error, userId: userProfile.user.user_id },
      'Error fetching expiring items summary',
    );
    next(error);
  }
});

/**
 * @openapi
 * /inventory/expiring:
 *   get:
 *     tags: [Inventory]
 *     summary: Get expiring items
 *     description: Get items expiring within a specified number of days.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: days
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 90
 *           default: 7
 *         description: Number of days to look ahead
 *     responses:
 *       200:
 *         description: Expiring items retrieved
 *       401:
 *         description: Unauthorized
 */
router.get(
  '/expiring',
  validateRequest(daysAheadQuerySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ExpiringItemsRequest = z.infer<typeof daysAheadQuerySchema>;
    const { query } = req as unknown as ExpiringItemsRequest;

    try {
      const items = await expiryService.getExpiringItems(
        userProfile.user.user_id,
        query.days,
        req.log,
      );
      sendSuccess(res, { items, total: items.length });
    } catch (error) {
      req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching expiring items');
      next(error);
    }
  },
);

/**
 * @openapi
 * /inventory/expired:
 *   get:
 *     tags: [Inventory]
 *     summary: Get expired items
 *     description: Get all items that have already expired.
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Expired items retrieved
 *       401:
 *         description: Unauthorized
 */
router.get('/expired', async (req: Request, res: Response, next: NextFunction) => {
  const userProfile = req.user as UserProfile;

  try {
    const items = await expiryService.getExpiredItems(userProfile.user.user_id, req.log);
    sendSuccess(res, { items, total: items.length });
  } catch (error) {
    req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching expired items');
    next(error);
  }
});

// ============================================================================
// ALERT SETTINGS ENDPOINTS
// NOTE: These routes MUST be defined BEFORE /:inventoryId to avoid path conflicts
// ============================================================================

/**
 * @openapi
 * /inventory/alerts:
 *   get:
 *     tags: [Inventory]
 *     summary: Get alert settings
 *     description: Get the user's expiry alert settings.
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Alert settings retrieved
 *       401:
 *         description: Unauthorized
 */
router.get('/alerts', async (req: Request, res: Response, next: NextFunction) => {
  const userProfile = req.user as UserProfile;

  try {
    const settings = await expiryService.getAlertSettings(userProfile.user.user_id, req.log);
    sendSuccess(res, settings);
  } catch (error) {
    req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching alert settings');
    next(error);
  }
});

/**
 * @openapi
 * /inventory/alerts/{alertMethod}:
 *   put:
 *     tags: [Inventory]
 *     summary: Update alert settings
 *     description: Update alert settings for a specific notification method.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: alertMethod
 *         required: true
 *         schema:
 *           type: string
 *           enum: [email, push, in_app]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               days_before_expiry:
 *                 type: integer
 *                 minimum: 1
 *                 maximum: 30
 *               is_enabled:
 *                 type: boolean
 *     responses:
 *       200:
 *         description: Alert settings updated
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized
 */
router.put(
  '/alerts/:alertMethod',
  validateRequest(updateAlertSettingsSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type UpdateAlertRequest = z.infer<typeof updateAlertSettingsSchema>;
    const { params, body } = req as unknown as UpdateAlertRequest;

    try {
      const settings = await expiryService.updateAlertSettings(
        userProfile.user.user_id,
        params.alertMethod,
        body,
        req.log,
      );
      sendSuccess(res, settings);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, alertMethod: params.alertMethod },
        'Error updating alert settings',
      );
      next(error);
    }
  },
);

// ============================================================================
// RECIPE SUGGESTIONS ENDPOINT
// NOTE: This route MUST be defined BEFORE /:inventoryId to avoid path conflicts
// ============================================================================

/**
 * @openapi
 * /inventory/recipes/suggestions:
 *   get:
 *     tags: [Inventory]
 *     summary: Get recipe suggestions for expiring items
 *     description: Get recipes that use items expiring soon to reduce food waste.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: days
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 90
 *           default: 7
 *         description: Consider items expiring within this many days
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 50
 *           default: 10
 *       - in: query
 *         name: offset
 *         schema:
 *           type: integer
 *           minimum: 0
 *           default: 0
 *     responses:
 *       200:
 *         description: Recipe suggestions retrieved
 *       401:
 *         description: Unauthorized
 */
router.get(
  '/recipes/suggestions',
  validateRequest(
    z.object({
      query: z.object({
        days: z
          .string()
          .optional()
          .default('7')
          .transform((val) => parseInt(val, 10))
          .pipe(z.number().int().min(1).max(90)),
        limit: optionalNumeric({ default: 10, min: 1, max: 50, integer: true }),
        offset: optionalNumeric({ default: 0, min: 0, integer: true }),
      }),
    }),
  ),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    const { query } = req as unknown as {
      query: { days: number; limit?: number; offset?: number };
    };

    try {
      const result = await expiryService.getRecipeSuggestionsForExpiringItems(
        userProfile.user.user_id,
        query.days,
        req.log,
        { limit: query.limit, offset: query.offset },
      );
      sendSuccess(res, result);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id },
        'Error fetching recipe suggestions',
      );
      next(error);
    }
  },
);

// ============================================================================
// INVENTORY ITEM BY ID ENDPOINTS
// NOTE: These routes with /:inventoryId MUST come AFTER specific path routes
// ============================================================================

/**
 * @openapi
 * /inventory/{inventoryId}:
 *   get:
 *     tags: [Inventory]
 *     summary: Get inventory item by ID
 *     description: Retrieve a specific inventory item.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: inventoryId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: Inventory item retrieved
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Item not found
 */
router.get(
  '/:inventoryId',
  validateRequest(inventoryIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type GetItemRequest = z.infer<typeof inventoryIdParamSchema>;
    const { params } = req as unknown as GetItemRequest;

    try {
      const item = await expiryService.getInventoryItemById(
        params.inventoryId,
        userProfile.user.user_id,
        req.log,
      );
      sendSuccess(res, item);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, inventoryId: params.inventoryId },
        'Error fetching inventory item',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /inventory/{inventoryId}:
 *   put:
 *     tags: [Inventory]
 *     summary: Update inventory item
 *     description: Update an existing inventory item.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: inventoryId
 *         required: true
 *         schema:
 *           type: integer
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               quantity:
 *                 type: number
 *                 minimum: 0
 *               unit:
 *                 type: string
 *                 maxLength: 50
 *               expiry_date:
 *                 type: string
 *                 format: date
 *               location:
 *                 type: string
 *                 enum: [fridge, freezer, pantry, room_temp]
 *               notes:
 *                 type: string
 *                 maxLength: 500
 *               is_consumed:
 *                 type: boolean
 *     responses:
 *       200:
 *         description: Item updated
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Item not found
 */
router.put(
  '/:inventoryId',
  validateRequest(updateInventoryItemSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type UpdateItemRequest = z.infer<typeof updateInventoryItemSchema>;
    const { params, body } = req as unknown as UpdateItemRequest;

    try {
      const item = await expiryService.updateInventoryItem(
        params.inventoryId,
        userProfile.user.user_id,
        body,
        req.log,
      );
      sendSuccess(res, item);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, inventoryId: params.inventoryId },
        'Error updating inventory item',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /inventory/{inventoryId}:
 *   delete:
 *     tags: [Inventory]
 *     summary: Delete inventory item
 *     description: Remove an item from the user's inventory.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: inventoryId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       204:
 *         description: Item deleted
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Item not found
 */
router.delete(
  '/:inventoryId',
  validateRequest(inventoryIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type DeleteItemRequest = z.infer<typeof inventoryIdParamSchema>;
    const { params } = req as unknown as DeleteItemRequest;

    try {
      await expiryService.deleteInventoryItem(
        params.inventoryId,
        userProfile.user.user_id,
        req.log,
      );
      sendNoContent(res);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, inventoryId: params.inventoryId },
        'Error deleting inventory item',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /inventory/{inventoryId}/consume:
 *   post:
 *     tags: [Inventory]
 *     summary: Mark item as consumed
 *     description: Mark an inventory item as consumed.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: inventoryId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       204:
 *         description: Item marked as consumed
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Item not found
 */
router.post(
  '/:inventoryId/consume',
  validateRequest(inventoryIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ConsumeItemRequest = z.infer<typeof inventoryIdParamSchema>;
    const { params } = req as unknown as ConsumeItemRequest;

    try {
      await expiryService.markItemConsumed(params.inventoryId, userProfile.user.user_id, req.log);
      sendNoContent(res);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, inventoryId: params.inventoryId },
        'Error marking item as consumed',
      );
      next(error);
    }
  },
);

export default router;
