// src/routes/receipt.routes.ts
/**
 * @file Receipt Scanning API Routes
 * Provides endpoints for uploading, processing, and managing scanned receipts.
 */
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 receiptService from '../services/receiptService.server';
import * as expiryService from '../services/expiryService.server';
import { createUploadMiddleware, handleMulterError } from '../middleware/multer.middleware';
import { receiptQueue } from '../services/queues.server';
import { requireFileUpload } from '../middleware/fileUpload.middleware';

const router = express.Router();

// Configure multer for receipt image uploads (max 10MB)
const receiptUpload = createUploadMiddleware({
  storageType: 'receipt',
  fileSize: 10 * 1024 * 1024, // 10MB
  fileFilter: 'image',
});

// --- Zod Schemas for Receipt Routes ---

/**
 * Receipt status validation
 */
const receiptStatusSchema = z.enum(['pending', 'processing', 'completed', 'failed']);

/**
 * Receipt item status validation
 */
const receiptItemStatusSchema = z.enum(['unmatched', 'matched', 'needs_review', 'ignored']);

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

/**
 * Schema for receipt ID parameter
 */
const receiptIdParamSchema = numericIdParam(
  'receiptId',
  "Invalid ID for parameter 'receiptId'. Must be a number.",
);

/**
 * Schema for receipt item ID parameter
 */
const _receiptItemIdParamSchema = numericIdParam(
  'itemId',
  "Invalid ID for parameter 'itemId'. Must be a number.",
);

/**
 * Schema for uploading a receipt (used with file upload, not base64)
 */
const uploadReceiptSchema = z.object({
  body: z.object({
    store_id: z
      .string()
      .optional()
      .transform((val) => (val ? parseInt(val, 10) : undefined))
      .pipe(z.number().int().positive().optional()),
    transaction_date: z.string().date('Transaction date must be in YYYY-MM-DD format.').optional(),
  }),
});

/**
 * Schema for receipt query
 */
const receiptQuerySchema = z.object({
  query: z.object({
    limit: optionalNumeric({ default: 50, min: 1, max: 100, integer: true }),
    offset: optionalNumeric({ default: 0, min: 0, integer: true }),
    status: receiptStatusSchema.optional(),
    store_id: z
      .string()
      .optional()
      .transform((val) => (val ? parseInt(val, 10) : undefined))
      .pipe(z.number().int().positive().optional()),
    from_date: z.string().date().optional(),
    to_date: z.string().date().optional(),
  }),
});

/**
 * Schema for updating a receipt item
 */
const updateReceiptItemSchema = z.object({
  params: z.object({
    receiptId: z.coerce.number().int().positive(),
    itemId: z.coerce.number().int().positive(),
  }),
  body: z
    .object({
      status: receiptItemStatusSchema.optional(),
      master_item_id: z.number().int().positive().nullable().optional(),
      product_id: z.number().int().positive().nullable().optional(),
      match_confidence: z.number().min(0).max(1).optional(),
    })
    .refine((data) => Object.keys(data).length > 0, {
      message: 'At least one field to update must be provided.',
    }),
});

/**
 * Schema for confirming receipt items to add to inventory
 */
const confirmItemsSchema = z.object({
  params: z.object({
    receiptId: z.coerce.number().int().positive(),
  }),
  body: z.object({
    items: z.array(
      z.object({
        receipt_item_id: z.number().int().positive(),
        item_name: z.string().max(255).optional(),
        quantity: z.number().positive().optional(),
        location: storageLocationSchema.optional(),
        expiry_date: z.string().date().optional(),
        include: z.boolean(),
      }),
    ),
  }),
});

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

// ============================================================================
// RECEIPT MANAGEMENT ENDPOINTS
// ============================================================================

/**
 * @openapi
 * /receipts:
 *   get:
 *     tags: [Receipts]
 *     summary: Get user's receipts
 *     description: Retrieve the user's scanned receipts with optional filtering.
 *     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: status
 *         schema:
 *           type: string
 *           enum: [pending, processing, completed, failed]
 *       - in: query
 *         name: store_id
 *         schema:
 *           type: integer
 *       - in: query
 *         name: from_date
 *         schema:
 *           type: string
 *           format: date
 *       - in: query
 *         name: to_date
 *         schema:
 *           type: string
 *           format: date
 *     responses:
 *       200:
 *         description: Receipts retrieved
 *       401:
 *         description: Unauthorized
 */
router.get(
  '/',
  validateRequest(receiptQuerySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ReceiptQueryRequest = z.infer<typeof receiptQuerySchema>;
    const { query } = req as unknown as ReceiptQueryRequest;

    try {
      const result = await receiptService.getReceipts(
        {
          user_id: userProfile.user.user_id,
          status: query.status,
          store_id: query.store_id,
          from_date: query.from_date,
          to_date: query.to_date,
          limit: query.limit,
          offset: query.offset,
        },
        req.log,
      );
      sendSuccess(res, result);
    } catch (error) {
      req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching receipts');
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts:
 *   post:
 *     tags: [Receipts]
 *     summary: Upload a receipt
 *     description: Upload a receipt image for processing and item extraction.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         multipart/form-data:
 *           schema:
 *             type: object
 *             required:
 *               - receipt
 *             properties:
 *               receipt:
 *                 type: string
 *                 format: binary
 *                 description: Receipt image file
 *               store_id:
 *                 type: integer
 *                 description: Store ID if known
 *               transaction_date:
 *                 type: string
 *                 format: date
 *                 description: Transaction date if known (YYYY-MM-DD)
 *     responses:
 *       201:
 *         description: Receipt uploaded and queued for processing
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized
 */
router.post(
  '/',
  receiptUpload.single('receipt'),
  requireFileUpload('receipt'),
  validateRequest(uploadReceiptSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type UploadReceiptRequest = z.infer<typeof uploadReceiptSchema>;
    const { body } = req as unknown as UploadReceiptRequest;
    const file = req.file as Express.Multer.File;

    try {
      req.log.info(
        { userId: userProfile.user.user_id, filename: file.filename },
        'Uploading receipt',
      );

      // Create receipt record with the actual file path
      const receipt = await receiptService.createReceipt(
        userProfile.user.user_id,
        file.path, // Use the actual file path from multer
        req.log,
        {
          storeId: body.store_id,
          transactionDate: body.transaction_date,
        },
      );

      // Queue the receipt for processing via BullMQ
      const bindings = req.log.bindings?.() || {};
      const job = await receiptQueue.add(
        'process-receipt',
        {
          receiptId: receipt.receipt_id,
          userId: userProfile.user.user_id,
          imagePath: file.path,
          meta: {
            requestId: bindings.request_id as string | undefined,
            userId: userProfile.user.user_id,
            origin: 'api',
          },
        },
        {
          jobId: `receipt-${receipt.receipt_id}`,
        },
      );

      req.log.info(
        { receiptId: receipt.receipt_id, jobId: job.id },
        'Receipt queued for processing',
      );

      sendSuccess(res, { ...receipt, job_id: job.id }, 201);
    } catch (error) {
      req.log.error({ error, userId: userProfile.user.user_id }, 'Error uploading receipt');
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts/{receiptId}:
 *   get:
 *     tags: [Receipts]
 *     summary: Get receipt by ID
 *     description: Retrieve a specific receipt with its extracted items.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: Receipt retrieved
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.get(
  '/:receiptId',
  validateRequest(receiptIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type GetReceiptRequest = z.infer<typeof receiptIdParamSchema>;
    const { params } = req as unknown as GetReceiptRequest;

    try {
      const receipt = await receiptService.getReceiptById(
        params.receiptId,
        userProfile.user.user_id,
        req.log,
      );

      // Also get the items
      const items = await receiptService.getReceiptItems(params.receiptId, req.log);

      sendSuccess(res, { receipt, items });
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error fetching receipt',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts/{receiptId}:
 *   delete:
 *     tags: [Receipts]
 *     summary: Delete receipt
 *     description: Delete a receipt and all associated data.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       204:
 *         description: Receipt deleted
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.delete(
  '/:receiptId',
  validateRequest(receiptIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type DeleteReceiptRequest = z.infer<typeof receiptIdParamSchema>;
    const { params } = req as unknown as DeleteReceiptRequest;

    try {
      await receiptService.deleteReceipt(params.receiptId, userProfile.user.user_id, req.log);
      sendNoContent(res);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error deleting receipt',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts/{receiptId}/reprocess:
 *   post:
 *     tags: [Receipts]
 *     summary: Reprocess receipt
 *     description: Queue a failed receipt for reprocessing.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: Receipt queued for reprocessing
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.post(
  '/:receiptId/reprocess',
  validateRequest(receiptIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ReprocessReceiptRequest = z.infer<typeof receiptIdParamSchema>;
    const { params } = req as unknown as ReprocessReceiptRequest;

    try {
      // Verify the receipt exists and belongs to user
      const receipt = await receiptService.getReceiptById(
        params.receiptId,
        userProfile.user.user_id,
        req.log,
      );

      // Queue for reprocessing via BullMQ
      const bindings = req.log.bindings?.() || {};
      const job = await receiptQueue.add(
        'process-receipt',
        {
          receiptId: receipt.receipt_id,
          userId: userProfile.user.user_id,
          imagePath: receipt.receipt_image_url, // Use stored image path
          meta: {
            requestId: bindings.request_id as string | undefined,
            userId: userProfile.user.user_id,
            origin: 'api-reprocess',
          },
        },
        {
          jobId: `receipt-${receipt.receipt_id}-reprocess-${Date.now()}`,
        },
      );

      req.log.info(
        { receiptId: params.receiptId, jobId: job.id },
        'Receipt queued for reprocessing',
      );

      sendSuccess(res, {
        message: 'Receipt queued for reprocessing',
        receipt_id: receipt.receipt_id,
        job_id: job.id,
      });
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error reprocessing receipt',
      );
      next(error);
    }
  },
);

// ============================================================================
// RECEIPT ITEMS ENDPOINTS
// ============================================================================

/**
 * @openapi
 * /receipts/{receiptId}/items:
 *   get:
 *     tags: [Receipts]
 *     summary: Get receipt items
 *     description: Get all extracted items from a receipt.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: Receipt items retrieved
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.get(
  '/:receiptId/items',
  validateRequest(receiptIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type GetItemsRequest = z.infer<typeof receiptIdParamSchema>;
    const { params } = req as unknown as GetItemsRequest;

    try {
      // Verify receipt belongs to user
      await receiptService.getReceiptById(params.receiptId, userProfile.user.user_id, req.log);

      const items = await receiptService.getReceiptItems(params.receiptId, req.log);
      sendSuccess(res, { items, total: items.length });
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error fetching receipt items',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts/{receiptId}/items/{itemId}:
 *   put:
 *     tags: [Receipts]
 *     summary: Update receipt item
 *     description: Update a receipt item's matching status or linked product.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *       - in: path
 *         name: itemId
 *         required: true
 *         schema:
 *           type: integer
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               status:
 *                 type: string
 *                 enum: [unmatched, matched, needs_review, ignored]
 *               master_item_id:
 *                 type: integer
 *                 nullable: true
 *               product_id:
 *                 type: integer
 *                 nullable: true
 *               match_confidence:
 *                 type: number
 *                 minimum: 0
 *                 maximum: 1
 *     responses:
 *       200:
 *         description: Item updated
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt or item not found
 */
router.put(
  '/:receiptId/items/:itemId',
  validateRequest(updateReceiptItemSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type UpdateItemRequest = z.infer<typeof updateReceiptItemSchema>;
    const { params, body } = req as unknown as UpdateItemRequest;

    try {
      // Verify receipt belongs to user
      await receiptService.getReceiptById(params.receiptId, userProfile.user.user_id, req.log);

      const item = await receiptService.updateReceiptItem(params.itemId, body, req.log);
      sendSuccess(res, item);
    } catch (error) {
      req.log.error(
        {
          error,
          userId: userProfile.user.user_id,
          receiptId: params.receiptId,
          itemId: params.itemId,
        },
        'Error updating receipt item',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts/{receiptId}/items/unadded:
 *   get:
 *     tags: [Receipts]
 *     summary: Get unadded items
 *     description: Get receipt items that haven't been added to inventory yet.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: Unadded items retrieved
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.get(
  '/:receiptId/items/unadded',
  validateRequest(receiptIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type GetUnaddedRequest = z.infer<typeof receiptIdParamSchema>;
    const { params } = req as unknown as GetUnaddedRequest;

    try {
      // Verify receipt belongs to user
      await receiptService.getReceiptById(params.receiptId, userProfile.user.user_id, req.log);

      const items = await receiptService.getUnaddedItems(params.receiptId, req.log);
      sendSuccess(res, { items, total: items.length });
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error fetching unadded receipt items',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /receipts/{receiptId}/confirm:
 *   post:
 *     tags: [Receipts]
 *     summary: Confirm items for inventory
 *     description: Confirm selected receipt items to add to user's inventory.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - items
 *             properties:
 *               items:
 *                 type: array
 *                 items:
 *                   type: object
 *                   required:
 *                     - receipt_item_id
 *                     - include
 *                   properties:
 *                     receipt_item_id:
 *                       type: integer
 *                     item_name:
 *                       type: string
 *                       maxLength: 255
 *                     quantity:
 *                       type: number
 *                       minimum: 0
 *                     location:
 *                       type: string
 *                       enum: [fridge, freezer, pantry, room_temp]
 *                     expiry_date:
 *                       type: string
 *                       format: date
 *                     include:
 *                       type: boolean
 *     responses:
 *       200:
 *         description: Items added to inventory
 *       400:
 *         description: Validation error
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.post(
  '/:receiptId/confirm',
  validateRequest(confirmItemsSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ConfirmItemsRequest = z.infer<typeof confirmItemsSchema>;
    const { params, body } = req as unknown as ConfirmItemsRequest;

    try {
      req.log.info(
        {
          userId: userProfile.user.user_id,
          receiptId: params.receiptId,
          itemCount: body.items.length,
        },
        'Confirming receipt items for inventory',
      );

      const addedItems = await expiryService.addItemsFromReceipt(
        userProfile.user.user_id,
        params.receiptId,
        body.items,
        req.log,
      );

      sendSuccess(res, { added_items: addedItems, count: addedItems.length });
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error confirming receipt items',
      );
      next(error);
    }
  },
);

// ============================================================================
// PROCESSING LOGS ENDPOINT
// ============================================================================

/**
 * @openapi
 * /receipts/{receiptId}/logs:
 *   get:
 *     tags: [Receipts]
 *     summary: Get processing logs
 *     description: Get the processing log history for a receipt.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: receiptId
 *         required: true
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: Processing logs retrieved
 *       401:
 *         description: Unauthorized
 *       404:
 *         description: Receipt not found
 */
router.get(
  '/:receiptId/logs',
  validateRequest(receiptIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type GetLogsRequest = z.infer<typeof receiptIdParamSchema>;
    const { params } = req as unknown as GetLogsRequest;

    try {
      // Verify receipt belongs to user
      await receiptService.getReceiptById(params.receiptId, userProfile.user.user_id, req.log);

      const logs = await receiptService.getProcessingLogs(params.receiptId, req.log);
      sendSuccess(res, { logs, total: logs.length });
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, receiptId: params.receiptId },
        'Error fetching processing logs',
      );
      next(error);
    }
  },
);

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

export default router;
