// src/routes/upc.routes.ts
/**
 * @file UPC Scanning API Routes
 * Provides endpoints for UPC barcode scanning, lookup, and scan history.
 */
import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import passport, { isAdmin } 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 upcService from '../services/upcService.server';

const router = express.Router();

// --- Zod Schemas for UPC Routes ---

/**
 * UPC code validation (8-14 digits)
 */
const upcCodeSchema = z.string().regex(/^[0-9]{8,14}$/, 'UPC code must be 8-14 digits.');

/**
 * Scan source validation
 */
const scanSourceSchema = z.enum(['image_upload', 'manual_entry', 'phone_app', 'camera_scan']);

/**
 * Schema for UPC scan request
 */
const scanUpcSchema = z.object({
  body: z
    .object({
      upc_code: z.string().optional(),
      image_base64: z.string().optional(),
      scan_source: scanSourceSchema,
    })
    .refine((data) => data.upc_code || data.image_base64, {
      message: 'Either upc_code or image_base64 must be provided.',
    }),
});

/**
 * Schema for UPC lookup request (without recording scan)
 */
const lookupUpcSchema = z.object({
  query: z.object({
    upc_code: upcCodeSchema,
    include_external: z
      .string()
      .optional()
      .transform((val) => val === 'true'),
    force_refresh: z
      .string()
      .optional()
      .transform((val) => val === 'true'),
  }),
});

/**
 * Schema for linking UPC to product (admin)
 */
const linkUpcSchema = z.object({
  body: z.object({
    upc_code: upcCodeSchema,
    product_id: z.number().int().positive('Product ID must be a positive integer.'),
  }),
});

/**
 * Schema for scan ID parameter
 */
const scanIdParamSchema = numericIdParam(
  'scanId',
  "Invalid ID for parameter 'scanId'. Must be a number.",
);

/**
 * Schema for scan history query
 */
const scanHistoryQuerySchema = z.object({
  query: z.object({
    limit: optionalNumeric({ default: 50, min: 1, max: 100, integer: true }),
    offset: optionalNumeric({ default: 0, min: 0, integer: true }),
    lookup_successful: z
      .string()
      .optional()
      .transform((val) => (val === 'true' ? true : val === 'false' ? false : undefined)),
    scan_source: scanSourceSchema.optional(),
    from_date: z.string().date().optional(),
    to_date: z.string().date().optional(),
  }),
});

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

/**
 * @openapi
 * /upc/scan:
 *   post:
 *     tags: [UPC Scanning]
 *     summary: Scan a UPC barcode
 *     description: >
 *       Scans a UPC barcode either from a manually entered code or from an image.
 *       Records the scan in history and returns product information if found.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - scan_source
 *             properties:
 *               upc_code:
 *                 type: string
 *                 pattern: '^[0-9]{8,14}$'
 *                 description: UPC code (8-14 digits). Required if image_base64 is not provided.
 *               image_base64:
 *                 type: string
 *                 description: Base64-encoded image containing a barcode. Required if upc_code is not provided.
 *               scan_source:
 *                 type: string
 *                 enum: [image_upload, manual_entry, phone_app, camera_scan]
 *                 description: How the scan was initiated.
 *     responses:
 *       200:
 *         description: Scan completed successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Validation error - invalid UPC code or missing data
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.post(
  '/scan',
  validateRequest(scanUpcSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ScanUpcRequest = z.infer<typeof scanUpcSchema>;
    const { body } = req as unknown as ScanUpcRequest;

    try {
      req.log.info(
        { userId: userProfile.user.user_id, scanSource: body.scan_source },
        'UPC scan request received',
      );

      const result = await upcService.scanUpc(userProfile.user.user_id, body, req.log);
      sendSuccess(res, result);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, scanSource: body.scan_source },
        'Error processing UPC scan',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /upc/lookup:
 *   get:
 *     tags: [UPC Scanning]
 *     summary: Look up a UPC code
 *     description: >
 *       Looks up product information for a UPC code without recording in scan history.
 *       Useful for verification or quick lookups.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: upc_code
 *         required: true
 *         schema:
 *           type: string
 *           pattern: '^[0-9]{8,14}$'
 *         description: UPC code to look up (8-14 digits)
 *       - in: query
 *         name: include_external
 *         schema:
 *           type: boolean
 *           default: true
 *         description: Whether to check external APIs if not found locally
 *       - in: query
 *         name: force_refresh
 *         schema:
 *           type: boolean
 *           default: false
 *         description: Skip cache and perform fresh external lookup
 *     responses:
 *       200:
 *         description: Lookup completed
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Invalid UPC code format
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.get(
  '/lookup',
  validateRequest(lookupUpcSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    type LookupUpcRequest = z.infer<typeof lookupUpcSchema>;
    const { query } = req as unknown as LookupUpcRequest;

    try {
      req.log.debug({ upcCode: query.upc_code }, 'UPC lookup request received');

      const result = await upcService.lookupUpc(
        {
          upc_code: query.upc_code,
          force_refresh: query.force_refresh,
        },
        req.log,
      );
      sendSuccess(res, result);
    } catch (error) {
      req.log.error({ error, upcCode: query.upc_code }, 'Error looking up UPC');
      next(error);
    }
  },
);

/**
 * @openapi
 * /upc/history:
 *   get:
 *     tags: [UPC Scanning]
 *     summary: Get scan history
 *     description: Retrieve the authenticated user's UPC scan history with optional filtering.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 100
 *           default: 50
 *         description: Maximum number of results
 *       - in: query
 *         name: offset
 *         schema:
 *           type: integer
 *           minimum: 0
 *           default: 0
 *         description: Number of results to skip
 *       - in: query
 *         name: lookup_successful
 *         schema:
 *           type: boolean
 *         description: Filter by lookup success status
 *       - in: query
 *         name: scan_source
 *         schema:
 *           type: string
 *           enum: [image_upload, manual_entry, phone_app, camera_scan]
 *         description: Filter by scan source
 *       - in: query
 *         name: from_date
 *         schema:
 *           type: string
 *           format: date
 *         description: Filter scans from this date (YYYY-MM-DD)
 *       - in: query
 *         name: to_date
 *         schema:
 *           type: string
 *           format: date
 *         description: Filter scans until this date (YYYY-MM-DD)
 *     responses:
 *       200:
 *         description: Scan history retrieved
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.get(
  '/history',
  validateRequest(scanHistoryQuerySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type ScanHistoryRequest = z.infer<typeof scanHistoryQuerySchema>;
    const { query } = req as unknown as ScanHistoryRequest;

    try {
      const result = await upcService.getScanHistory(
        {
          user_id: userProfile.user.user_id,
          limit: query.limit,
          offset: query.offset,
          lookup_successful: query.lookup_successful,
          scan_source: query.scan_source,
          from_date: query.from_date,
          to_date: query.to_date,
        },
        req.log,
      );
      sendSuccess(res, result);
    } catch (error) {
      req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching scan history');
      next(error);
    }
  },
);

/**
 * @openapi
 * /upc/history/{scanId}:
 *   get:
 *     tags: [UPC Scanning]
 *     summary: Get scan by ID
 *     description: Retrieve a specific scan record by its ID.
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: scanId
 *         required: true
 *         schema:
 *           type: integer
 *         description: Scan ID
 *     responses:
 *       200:
 *         description: Scan record retrieved
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       404:
 *         description: Scan record not found
 */
router.get(
  '/history/:scanId',
  validateRequest(scanIdParamSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type GetScanRequest = z.infer<typeof scanIdParamSchema>;
    const { params } = req as unknown as GetScanRequest;

    try {
      const scan = await upcService.getScanById(params.scanId, userProfile.user.user_id, req.log);
      sendSuccess(res, scan);
    } catch (error) {
      req.log.error(
        { error, userId: userProfile.user.user_id, scanId: params.scanId },
        'Error fetching scan by ID',
      );
      next(error);
    }
  },
);

/**
 * @openapi
 * /upc/stats:
 *   get:
 *     tags: [UPC Scanning]
 *     summary: Get scan statistics
 *     description: Get scanning statistics for the authenticated user.
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Scan statistics retrieved
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                 data:
 *                   type: object
 *                   properties:
 *                     total_scans:
 *                       type: integer
 *                     successful_lookups:
 *                       type: integer
 *                     unique_products:
 *                       type: integer
 *                     scans_today:
 *                       type: integer
 *                     scans_this_week:
 *                       type: integer
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.get('/stats', async (req: Request, res: Response, next: NextFunction) => {
  const userProfile = req.user as UserProfile;

  try {
    const stats = await upcService.getScanStats(userProfile.user.user_id, req.log);
    sendSuccess(res, stats);
  } catch (error) {
    req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching scan statistics');
    next(error);
  }
});

/**
 * @openapi
 * /upc/link:
 *   post:
 *     tags: [UPC Scanning]
 *     summary: Link UPC to product (Admin)
 *     description: >
 *       Links a UPC code to an existing product in the database.
 *       This is an admin-only operation.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - upc_code
 *               - product_id
 *             properties:
 *               upc_code:
 *                 type: string
 *                 pattern: '^[0-9]{8,14}$'
 *                 description: UPC code to link (8-14 digits)
 *               product_id:
 *                 type: integer
 *                 description: Product ID to link the UPC to
 *     responses:
 *       204:
 *         description: UPC linked successfully
 *       400:
 *         description: Invalid UPC code or product ID
 *       401:
 *         description: Unauthorized - invalid or missing token
 *       403:
 *         description: Forbidden - user is not an admin
 *       404:
 *         description: Product not found
 *       409:
 *         description: UPC code already linked to another product
 */
router.post(
  '/link',
  isAdmin, // Admin role check - only admins can link UPC codes to products
  validateRequest(linkUpcSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    const userProfile = req.user as UserProfile;
    type LinkUpcRequest = z.infer<typeof linkUpcSchema>;
    const { body } = req as unknown as LinkUpcRequest;

    try {
      req.log.info(
        { userId: userProfile.user.user_id, productId: body.product_id, upcCode: body.upc_code },
        'UPC link request received',
      );

      await upcService.linkUpcToProduct(body.product_id, body.upc_code, req.log);
      sendNoContent(res);
    } catch (error) {
      req.log.error(
        {
          error,
          userId: userProfile.user.user_id,
          productId: body.product_id,
          upcCode: body.upc_code,
        },
        'Error linking UPC to product',
      );
      next(error);
    }
  },
);

export default router;
