import { randomUUID } from 'node:crypto';
import { mkdir, writeFile } from 'node:fs/promises';
import { extname, resolve } from 'node:path';
import { Router } from 'express';
import multer from 'multer';
import type { AppEnv } from '../config/env.js';
import type { Database } from '../db/types.js';
import { invoiceOperationalStatus } from '../domain/delivery.js';
import { ConflictError, NotFoundError } from '../domain/errors.js';
import { escapeLike, normalizeIdentifier, normalizePersian } from '../domain/normalization.js';
import type { DeliveryService } from '../services/delivery-service.js';
import { FinancialWorkbookParser, sha256, type FinancialImportService } from '../services/excel-import-service.js';
import type { InvoiceService } from '../services/invoice-service.js';
import { formatJalaliDateTime, reportRange } from '../services/jalali.js';
import { PERMISSIONS } from '../services/rbac.js';
import type { ReportService } from '../services/report-service.js';
import type { PushService } from '../services/push-service.js';
import { appendAudit } from '../services/audit.js';
import { auditContext, csrfProtection, requireAuth, requirePermission, setFlash } from './middleware.js';

interface Dependencies {
  db: Database;
  env: AppEnv;
  deliveries: DeliveryService;
  invoices: InvoiceService;
  imports: FinancialImportService;
  reports: ReportService;
  push: PushService;
}

const asArray = (value: unknown): string[] => value === undefined ? [] : Array.isArray(value) ? value.map(String) : [String(value)];

function queueRowsSql() {
  return `SELECT dr.id, dr.status, dr.delivery_type, dr.whole_invoice_no_lines, dr.approved_at,
    i.invoice_number, i.customer_name, rq.name AS requester, u.name AS manager,
    COALESCE((SELECT COUNT(*) FROM delivery_request_items x WHERE x.delivery_request_id = dr.id), 0) AS item_count
    FROM delivery_requests dr JOIN invoices i ON i.id = dr.invoice_id JOIN requesters rq ON rq.id = dr.requester_id
    JOIN users u ON u.id = dr.approved_by`;
}

export function operationalRoutes(deps: Dependencies): Router {
  const router = Router();
  const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: deps.env.MAX_UPLOAD_MB * 1024 * 1024, files: 1 } });
  router.use(requireAuth);

  router.get('/', async (req, res, next) => {
    try {
      const isWarehouse = req.currentUser!.roles.includes('warehouse_operator') && !req.currentUser!.permissions.has(PERMISSIONS.INVOICES_VIEW_ALL);
      const [invoices, pending, discrepancies, unread] = await Promise.all([
        deps.db.query<{ count: string }>(`SELECT COUNT(*) AS count FROM invoices WHERE archived_at IS NULL`),
        deps.db.query<{ count: string }>(
          isWarehouse
            ? `SELECT COUNT(*) AS count FROM delivery_requests WHERE warehouse_id = $1 AND status IN ('pending','returned')`
            : `SELECT COUNT(*) AS count FROM delivery_requests WHERE status IN ('pending','returned')`,
          isWarehouse ? [req.currentUser!.warehouseId] : []
        ),
        deps.db.query<{ count: string }>(`SELECT COUNT(*) AS count FROM discrepancies WHERE status = 'open'`),
        deps.db.query<{ count: string }>(`SELECT COUNT(*) AS count FROM notifications WHERE user_id = $1 AND read_at IS NULL`, [req.currentUser!.id])
      ]);
      let queue: Record<string, unknown>[] = [];
      if (isWarehouse && req.currentUser!.warehouseId) {
        queue = (await deps.db.query(
          `${queueRowsSql()} WHERE dr.warehouse_id = $1 AND dr.status IN ('pending','returned') ORDER BY dr.approved_at DESC LIMIT 8`,
          [req.currentUser!.warehouseId]
        )).rows;
      }
      res.render('dashboard', {
        title: 'داشبورد', metrics: { invoices: Number(invoices.rows[0]!.count), pending: Number(pending.rows[0]!.count),
          discrepancies: Number(discrepancies.rows[0]!.count), unread: Number(unread.rows[0]!.count) },
        isWarehouse, queue, formatJalaliDateTime
      });
    } catch (error) { next(error); }
  });

  router.get('/invoices', requirePermission(PERMISSIONS.INVOICES_VIEW_ALL), async (req, res, next) => {
    try {
      const page = Math.max(1, Number(req.query.page) || 1);
      const limit = 25;
      const params: unknown[] = [];
      const where: string[] = [];
      if (req.query.q) {
        params.push(`%${escapeLike(normalizeIdentifier(req.query.q))}%`);
        where.push(`i.normalized_number LIKE $${params.length} ESCAPE '\\'`);
      }
      const archived = req.query.archived === '1';
      where.push(archived ? 'i.archived_at IS NOT NULL' : 'i.archived_at IS NULL');
      if (req.query.discrepancy === '1') where.push('i.has_discrepancy = true');
      if (req.query.deliveryType) {
        params.push(req.query.deliveryType);
        where.push(`EXISTS(SELECT 1 FROM delivery_requests f WHERE f.invoice_id = i.id AND f.delivery_type = $${params.length})`);
      }
      params.push(limit, (page - 1) * limit);
      const result = await deps.db.query<{
        id: string; invoice_number: string; customer_name: string | null; has_discrepancy: boolean; manager_edited: boolean;
        archived_at: Date | null; total: string; delivered: string; has_pending: boolean; has_cancelled: boolean; has_returned: boolean; updated_at: Date;
      }>(
        `SELECT i.id, i.invoice_number, i.customer_name, i.has_discrepancy, i.manager_edited, i.archived_at, i.updated_at,
          COALESCE((SELECT SUM(quantity) FROM invoice_version_items vi WHERE vi.invoice_version_id = i.operational_version_id),0) AS total,
          COALESCE((SELECT SUM(dri.approved_quantity) FROM delivery_request_items dri JOIN delivery_requests dr ON dr.id = dri.delivery_request_id
            WHERE dr.invoice_id = i.id AND dr.status = 'delivered'),0) AS delivered,
          EXISTS(SELECT 1 FROM delivery_requests dr WHERE dr.invoice_id = i.id AND dr.status = 'pending') AS has_pending,
          EXISTS(SELECT 1 FROM delivery_requests dr WHERE dr.invoice_id = i.id AND dr.status = 'cancelled') AS has_cancelled,
          EXISTS(SELECT 1 FROM delivery_requests dr WHERE dr.invoice_id = i.id AND dr.status = 'returned') AS has_returned
         FROM invoices i WHERE ${where.join(' AND ')} ORDER BY i.updated_at DESC LIMIT $${params.length - 1} OFFSET $${params.length}`,
        params
      );
      const rows = result.rows.map((row) => ({ ...row, status: invoiceOperationalStatus({
        archived: Boolean(row.archived_at), hasDiscrepancy: row.has_discrepancy, totalQuantity: Number(row.total),
        deliveredQuantity: Number(row.delivered), hasPending: row.has_pending, hasCancelled: row.has_cancelled, hasReturned: row.has_returned
      }) }));
      res.render('invoices/index', { title: archived ? 'بایگانی فاکتورها' : 'فاکتورها', rows, page, archived, query: req.query, formatJalaliDateTime });
    } catch (error) { next(error); }
  });

  router.get('/invoices/create', requirePermission(PERMISSIONS.INVOICES_MANAGE), async (_req, res, next) => {
    try {
      const [requesters, products] = await Promise.all([
        deps.db.query(`SELECT id, name FROM requesters WHERE active = true AND archived_at IS NULL ORDER BY name LIMIT 50`),
        deps.db.query(`SELECT id, code, name, unit FROM products WHERE active = true AND archived_at IS NULL ORDER BY name LIMIT 50`)
      ]);
      res.render('invoices/create', { title: 'ثبت فاکتور دستی', requesters: requesters.rows, products: products.rows });
    } catch (error) { next(error); }
  });

  router.post('/invoices', requirePermission(PERMISSIONS.INVOICES_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      let items: any[] = [];
      if (req.body.itemsJson) {
        try { items = JSON.parse(String(req.body.itemsJson)); } catch { throw new ConflictError('فهرست اقلام معتبر نیست.'); }
      }
      const id = await deps.invoices.createManual(req.currentUser!.id, {
        invoiceNumber: String(req.body.invoiceNumber ?? ''), requesterId: String(req.body.requesterId ?? ''),
        customerName: String(req.body.customerName ?? ''), customerCode: String(req.body.customerCode ?? ''),
        notes: String(req.body.notes ?? ''), items: items.map((item) => ({
          productId: item.productId || null, productCode: String(item.productCode ?? ''), productName: String(item.productName ?? ''),
          unit: String(item.unit ?? ''), quantity: Number(item.quantity ?? 1)
        }))
      }, auditContext(req));
      setFlash(req, 'success', `فاکتور ${req.body.invoiceNumber} با موفقیت ثبت شد.`);
      res.redirect(`/invoices/${id}`);
    } catch (error) { next(error); }
  });

  router.get('/invoices/:id', requirePermission(PERMISSIONS.INVOICES_VIEW_ALL), async (req, res, next) => {
    try {
      const invoice = await deps.db.query<any>('SELECT * FROM invoices WHERE id = $1', [req.params.id]);
      if (!invoice.rowCount) throw new NotFoundError('فاکتور یافت نشد.');
      const [items, requests, versions, requesters, warehouses, discrepancies] = await Promise.all([
        deps.db.query<any>(`SELECT * FROM invoice_version_items WHERE invoice_version_id = $1 ORDER BY line_number`, [invoice.rows[0]!.operational_version_id]),
        deps.db.query<any>(
          `SELECT dr.*, rq.name AS requester, u.name AS approver,
           COALESCE((SELECT json_agg(json_build_object('type',de.event_type,'at',de.occurred_at,'reason',de.reason,'actor',eu.name) ORDER BY de.sequence)
            FROM delivery_events de JOIN users eu ON eu.id = de.actor_id WHERE de.delivery_request_id = dr.id), '[]'::json) AS events
           FROM delivery_requests dr JOIN requesters rq ON rq.id = dr.requester_id JOIN users u ON u.id = dr.approved_by
           WHERE dr.invoice_id = $1 ORDER BY dr.approved_at DESC`, [req.params.id]),
        deps.db.query<any>(`SELECT * FROM invoice_versions WHERE invoice_id = $1 ORDER BY version_number DESC`, [req.params.id]),
        deps.db.query<any>(`SELECT id, name FROM requesters WHERE active = true AND archived_at IS NULL ORDER BY name`),
        deps.db.query<any>(`SELECT id, name FROM warehouses WHERE active = true AND archived_at IS NULL ORDER BY name`),
        deps.db.query<any>(`SELECT * FROM discrepancies WHERE invoice_id = $1 ORDER BY created_at DESC`, [req.params.id])
      ]);
      const remaining = await deps.deliveries.remainingQuantities(deps.db, String(req.params.id));
      res.render('invoices/show', {
        title: `فاکتور ${invoice.rows[0]!.invoice_number}`, invoice: invoice.rows[0],
        items: items.rows.map((item) => ({ ...item, remaining: remaining.get(item.product_code) ?? 0 })), requests: requests.rows,
        versions: versions.rows, requesters: requesters.rows, warehouses: warehouses.rows, discrepancies: discrepancies.rows,
        idempotencyKey: randomUUID(), formatJalaliDateTime
      });
    } catch (error) { next(error); }
  });

  router.post('/invoices/:id/approve', requirePermission(PERMISSIONS.DELIVERIES_APPROVE), csrfProtection, async (req, res, next) => {
    try {
      const versionItems = await deps.db.query<any>(
        `SELECT vi.* FROM invoice_version_items vi JOIN invoices i ON i.operational_version_id = vi.invoice_version_id WHERE i.id = $1 ORDER BY vi.line_number`,
        [req.params.id]
      );
      const remaining = await deps.deliveries.remainingQuantities(deps.db, String(req.params.id));
      const deliveryType = req.body.deliveryType === 'partial' ? 'partial' : 'complete';
      const selectedIds = new Set(asArray(req.body.selectedItems));
      const chosen = versionItems.rows.filter((item) => {
        const available = remaining.get(item.product_code) ?? 0;
        return available > 0 && (deliveryType === 'complete' || selectedIds.has(item.id));
      }).map((item) => ({
        productId: item.product_id, productCode: item.product_code, productName: item.product_name, unit: item.unit,
        quantity: deliveryType === 'complete' ? remaining.get(item.product_code)! : Number(req.body[`quantity_${item.id}`] ?? remaining.get(item.product_code))
      }));
      const wholeNoLines = versionItems.rowCount === 0;
      const result = await deps.deliveries.approve({ id: req.currentUser!.id }, {
        invoiceId: String(req.params.id), requesterId: String(req.body.requesterId ?? ''), warehouseId: String(req.body.warehouseId ?? ''),
        deliveryType: wholeNoLines ? 'complete' : deliveryType, wholeInvoiceNoLines: wholeNoLines, items: chosen,
        notes: String(req.body.notes ?? ''), idempotencyKey: String(req.body.idempotencyKey ?? randomUUID())
      }, auditContext(req));
      const invoice = await deps.db.query<{ invoice_number: string }>('SELECT invoice_number FROM invoices WHERE id = $1', [req.params.id]);
      setFlash(req, 'success', deliveryType === 'complete'
        ? `خروج فاکتور ${invoice.rows[0]!.invoice_number} با موفقیت ثبت شد.`
        : `خروج جزئی فاکتور ${invoice.rows[0]!.invoice_number} با موفقیت ثبت شد.`);
      res.redirect(`/invoices/${req.params.id}#delivery-${result.id}`);
    } catch (error) { next(error); }
  });

  router.post('/invoices/:id/archive', requirePermission(PERMISSIONS.INVOICES_ARCHIVE), csrfProtection, async (req, res, next) => {
    try {
      await deps.invoices.archive(req.currentUser!.id, String(req.params.id), String(req.body.reason ?? ''), auditContext(req));
      setFlash(req, 'success', 'فاکتور با موفقیت بایگانی شد.'); res.redirect('/invoices');
    } catch (error) { next(error); }
  });
  router.post('/invoices/:id/restore', requirePermission(PERMISSIONS.INVOICES_ARCHIVE), csrfProtection, async (req, res, next) => {
    try {
      await deps.invoices.restore(req.currentUser!.id, String(req.params.id), auditContext(req));
      setFlash(req, 'success', 'فاکتور با موفقیت از بایگانی خارج شد.'); res.redirect(`/invoices/${req.params.id}`);
    } catch (error) { next(error); }
  });

  router.get('/warehouse', requirePermission(PERMISSIONS.DELIVERIES_WAREHOUSE_VIEW), async (req, res, next) => {
    try {
      const canViewAll = req.currentUser!.permissions.has(PERMISSIONS.INVOICES_VIEW_ALL);
      const rows = await deps.db.query<any>(
        `${queueRowsSql()} WHERE ${canViewAll ? 'true' : 'dr.warehouse_id = $1'} AND dr.status IN ('pending','returned') ORDER BY dr.approved_at DESC`,
        canViewAll ? [] : [req.currentUser!.warehouseId]
      );
      if (req.get('X-Partial') === '1') return res.render('warehouse/_queue', { rows: rows.rows, formatJalaliDateTime });
      res.render('warehouse/index', { title: 'صف بلادرنگ انبار', rows: rows.rows, formatJalaliDateTime });
    } catch (error) { next(error); }
  });

  router.get('/warehouse/:id', requirePermission(PERMISSIONS.DELIVERIES_WAREHOUSE_VIEW), async (req, res, next) => {
    try {
      const canViewAll = req.currentUser!.permissions.has(PERMISSIONS.INVOICES_VIEW_ALL);
      const request = await deps.db.query<any>(
        `SELECT dr.*, i.invoice_number, i.customer_name, rq.name AS requester, u.name AS approver
         FROM delivery_requests dr JOIN invoices i ON i.id = dr.invoice_id JOIN requesters rq ON rq.id = dr.requester_id
         JOIN users u ON u.id = dr.approved_by WHERE dr.id = $1 ${canViewAll ? '' : 'AND dr.warehouse_id = $2'}`,
        canViewAll ? [req.params.id] : [req.params.id, req.currentUser!.warehouseId]
      );
      if (!request.rowCount) throw new NotFoundError('درخواست مرتبط با انبار شما یافت نشد.');
      const [items, events] = await Promise.all([
        deps.db.query<any>('SELECT * FROM delivery_request_items WHERE delivery_request_id = $1 ORDER BY product_name', [req.params.id]),
        deps.db.query<any>(
          `SELECT de.*, u.name AS actor FROM delivery_events de JOIN users u ON u.id = de.actor_id
           WHERE de.delivery_request_id = $1 ORDER BY de.sequence`, [req.params.id])
      ]);
      res.render('warehouse/show', { title: `تحویل فاکتور ${request.rows[0]!.invoice_number}`, request: request.rows[0],
        items: items.rows, events: events.rows, idempotencyKey: randomUUID(), formatJalaliDateTime,
        canAct: request.rows[0]!.warehouse_id === req.currentUser!.warehouseId });
    } catch (error) { next(error); }
  });

  router.post('/warehouse/:id/:action', requirePermission(PERMISSIONS.DELIVERIES_WAREHOUSE_ACT), csrfProtection, async (req, res, next) => {
    try {
      const mapping: Record<string, 'deliver' | 'cancel' | 'return'> = { deliver: 'deliver', cancel: 'cancel', return: 'return' };
      const action = mapping[String(req.params.action)];
      if (!action) throw new NotFoundError();
      await deps.deliveries.warehouseAction(
        { id: req.currentUser!.id, warehouseId: req.currentUser!.warehouseId }, String(req.params.id), action,
        String(req.body.idempotencyKey ?? randomUUID()), String(req.body.reason ?? ''), auditContext(req)
      );
      const messages: Record<'deliver' | 'cancel' | 'return', string> = { deliver: 'تحویل کالا با موفقیت ثبت شد.', cancel: 'لغو تحویل با موفقیت ثبت شد.', return: 'برگشت از تحویل با موفقیت ثبت شد.' };
      setFlash(req, 'success', messages[action]); res.redirect(action === 'return' ? `/warehouse/${req.params.id}` : '/warehouse');
    } catch (error) { next(error); }
  });

  router.get('/imports', requirePermission(PERMISSIONS.IMPORTS_INVOICES), async (_req, res, next) => {
    try {
      const imports = await deps.db.query<any>(`SELECT im.*, u.name AS importer FROM imports im JOIN users u ON u.id = im.imported_by ORDER BY im.created_at DESC LIMIT 100`);
      res.render('imports/index', { title: 'ورود فایل مالی', imports: imports.rows, formatJalaliDateTime });
    } catch (error) { next(error); }
  });

  router.post('/imports', requirePermission(PERMISSIONS.IMPORTS_INVOICES), upload.single('file'), csrfProtection, async (req, res, next) => {
    try {
      if (!req.file) throw new ConflictError('انتخاب فایل الزامی است.');
      FinancialWorkbookParser.validateEnvelope(req.file.originalname, req.file.mimetype, req.file.buffer, deps.env.MAX_UPLOAD_MB * 1024 * 1024);
      const checksum = sha256(req.file.buffer);
      const dir = resolve('storage/private/imports'); await mkdir(dir, { recursive: true });
      const path = resolve(dir, `${randomUUID()}${extname(req.file.originalname).toLowerCase()}`);
      const registered = await deps.imports.register({ filename: req.file.originalname, storedPath: path, mimeType: req.file.mimetype,
        fileSize: req.file.size, checksum, actorId: req.currentUser!.id });
      if (!registered.duplicate) await writeFile(path, req.file.buffer, { flag: 'wx' });
      setFlash(req, registered.duplicate ? 'info' : 'success', registered.duplicate ? 'این فایل قبلاً ثبت شده است؛ نتیجه قبلی نمایش داده می‌شود.' : 'فایل با موفقیت بارگذاری و برای بررسی آماده شد.');
      res.redirect(`/imports/${registered.id}/preview`);
    } catch (error) { next(error); }
  });

  router.get('/imports/:id/preview', requirePermission(PERMISSIONS.IMPORTS_INVOICES), async (req, res, next) => {
    try {
      const parsed = await deps.imports.parseStored(String(req.params.id));
      const preview = await deps.imports.preview(parsed);
      await deps.db.query(`UPDATE imports SET status = CASE WHEN status = 'uploaded' THEN 'validated' ELSE status END,
        valid_rows = $2, rejected_rows = $3, outcome = $4::jsonb, progress = GREATEST(progress, 25) WHERE id = $1`,
        [req.params.id, parsed.rows.length, parsed.errors.length, JSON.stringify(preview)]);
      res.render('imports/preview', { title: 'پیش‌نمایش ورود مالی', importId: req.params.id, preview,
        sample: parsed.rows.slice(0, 30), errors: parsed.errors.slice(0, 100) });
    } catch (error) { next(error); }
  });

  router.post('/imports/:id/confirm', requirePermission(PERMISSIONS.IMPORTS_INVOICES), csrfProtection, async (req, res, next) => {
    try {
      const parsed = await deps.imports.parseStored(String(req.params.id));
      if (parsed.rows.length >= deps.env.IMPORT_QUEUE_THRESHOLD_ROWS) {
        await deps.db.transaction(async (tx) => {
          await tx.query(`UPDATE imports SET status = 'queued', progress = 30 WHERE id = $1 AND status <> 'completed'`, [req.params.id]);
          await tx.query(`INSERT INTO jobs(id, queue, type, payload) VALUES ($1,'imports','financial_import',$2::jsonb)`,
            [randomUUID(), JSON.stringify({ importId: req.params.id })]);
        });
        setFlash(req, 'success', 'ورود فایل در صف پردازش قرار گرفت. پیشرفت بدون نیاز به بارگذاری مجدد صفحه به‌روزرسانی می‌شود.');
      } else {
        const outcome = await deps.imports.confirm(String(req.params.id), parsed, auditContext(req));
        setFlash(req, 'success', `ورود فایل کامل شد: ${outcome.uniqueInvoices} فاکتور و ${outcome.validRows} ردیف معتبر.`);
      }
      res.redirect('/imports');
    } catch (error) { next(error); }
  });

  router.get('/imports/:id/errors.csv', requirePermission(PERMISSIONS.IMPORTS_INVOICES), async (req, res, next) => {
    try {
      const parsed = await deps.imports.parseStored(String(req.params.id));
      const csv = ['ردیف,دلایل,مقادیر', ...parsed.errors.map((error) =>
        `${error.rowNumber},"${error.reasons.join(' | ').replaceAll('"', '""')}","${error.rawValues.join(' | ').replaceAll('"', '""')}"`
      )].join('\r\n');
      res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      res.setHeader('Content-Disposition', `attachment; filename="import-${req.params.id}-errors.csv"`);
      res.send(`\uFEFF${csv}`);
    } catch (error) { next(error); }
  });

  router.get('/discrepancies', requirePermission(PERMISSIONS.DISCREPANCIES_RESOLVE), async (_req, res, next) => {
    try {
      const rows = await deps.db.query<any>(
        `SELECT d.*, i.invoice_number, i.customer_name FROM discrepancies d JOIN invoices i ON i.id = d.invoice_id
         ORDER BY CASE WHEN d.status = 'open' THEN 0 ELSE 1 END, d.created_at DESC`
      );
      res.render('invoices/discrepancies', { title: 'مغایرت‌ها', rows: rows.rows, formatJalaliDateTime });
    } catch (error) { next(error); }
  });

  router.get('/discrepancies/:id', requirePermission(PERMISSIONS.DISCREPANCIES_RESOLVE), async (req, res, next) => {
    try {
      const discrepancy = await deps.db.query<any>(
        `SELECT d.*, i.invoice_number FROM discrepancies d JOIN invoices i ON i.id = d.invoice_id WHERE d.id = $1`, [req.params.id]
      );
      if (!discrepancy.rowCount) throw new NotFoundError('مغایرت یافت نشد.');
      const row = discrepancy.rows[0]!;
      const [manual, financial, manualItems, financialItems] = await Promise.all([
        deps.db.query<any>('SELECT * FROM invoice_versions WHERE id = $1', [row.manual_version_id]),
        deps.db.query<any>('SELECT * FROM invoice_versions WHERE id = $1', [row.financial_version_id]),
        deps.db.query<any>('SELECT * FROM invoice_version_items WHERE invoice_version_id = $1 ORDER BY line_number', [row.manual_version_id]),
        deps.db.query<any>('SELECT * FROM invoice_version_items WHERE invoice_version_id = $1 ORDER BY line_number', [row.financial_version_id])
      ]);
      res.render('invoices/discrepancy-show', { title: `رفع مغایرت فاکتور ${row.invoice_number}`, discrepancy: row,
        manual: manual.rows[0], financial: financial.rows[0], manualItems: manualItems.rows, financialItems: financialItems.rows });
    } catch (error) { next(error); }
  });

  router.post('/discrepancies/:id/resolve', requirePermission(PERMISSIONS.DISCREPANCIES_RESOLVE), csrfProtection, async (req, res, next) => {
    try {
      const fields: Record<string, 'manager' | 'financial'> = {};
      const items: Record<string, 'manager' | 'financial'> = {};
      for (const [key, value] of Object.entries(req.body)) {
        if (key.startsWith('field_')) fields[key.slice(6)] = value === 'manager' ? 'manager' : 'financial';
        if (key.startsWith('item_')) items[key.slice(5)] = value === 'manager' ? 'manager' : 'financial';
      }
      await deps.invoices.resolveDiscrepancy(req.currentUser!.id, String(req.params.id), fields, items, String(req.body.reason ?? ''), auditContext(req));
      setFlash(req, 'success', 'مغایرت با ثبت انتخاب‌های سطح فیلد و قلم رفع شد.'); res.redirect('/discrepancies');
    } catch (error) { next(error); }
  });

  router.get('/reports', requirePermission(PERMISSIONS.REPORTS_VIEW), async (req, res, next) => {
    try {
      const range = reportRange({ preset: (req.query.preset as any) ?? 'today', startDate: req.query.startDate as string, endDate: req.query.endDate as string });
      const preview = await deps.reports.preview(range);
      res.render('reports/index', { title: 'گزارش‌ها', preview, range, query: req.query });
    } catch (error) { next(error); }
  });

  router.post('/reports/export', requirePermission(PERMISSIONS.REPORTS_VIEW), csrfProtection, async (req, res, next) => {
    try {
      const range = reportRange({ preset: req.body.preset, startDate: req.body.startDate, endDate: req.body.endDate });
      const filename = `mandegar-delivery-${Date.now()}.xlsx`;
      const path = await deps.reports.save(range, filename);
      await appendAudit(deps.db, auditContext(req), { action: 'report.exported', entityType: 'report', entityId: filename,
        newValues: { range: range.label } });
      res.download(path, filename);
    } catch (error) { next(error); }
  });

  router.get('/notifications', async (req, res, next) => {
    try {
      const rows = await deps.db.query<any>('SELECT * FROM notifications WHERE user_id = $1 ORDER BY created_at DESC LIMIT 100', [req.currentUser!.id]);
      res.render('notifications', { title: 'اعلان‌ها', rows: rows.rows, formatJalaliDateTime });
    } catch (error) { next(error); }
  });
  router.post('/notifications/read-all', csrfProtection, async (req, res, next) => {
    try { await deps.db.query('UPDATE notifications SET read_at = now() WHERE user_id = $1 AND read_at IS NULL', [req.currentUser!.id]); res.redirect('/notifications'); }
    catch (error) { next(error); }
  });
  router.post('/notifications/subscribe', csrfProtection, async (req, res, next) => {
    try {
      const subscription = req.body;
      if (!subscription?.endpoint || !subscription?.keys?.p256dh || !subscription?.keys?.auth) throw new ConflictError('اطلاعات اشتراک اعلان نامعتبر است.');
      await deps.db.query(
        `INSERT INTO web_push_subscriptions(id, user_id, endpoint, p256dh, auth, user_agent)
         VALUES ($1,$2,$3,$4,$5,$6)
         ON CONFLICT(endpoint) DO UPDATE SET user_id = EXCLUDED.user_id, p256dh = EXCLUDED.p256dh, auth = EXCLUDED.auth,
           user_agent = EXCLUDED.user_agent, updated_at = now()`,
        [randomUUID(), req.currentUser!.id, subscription.endpoint, subscription.keys.p256dh, subscription.keys.auth, req.get('user-agent') ?? null]
      );
      res.status(204).end();
    } catch (error) { next(error); }
  });

  router.get('/api/products/search', requirePermission(PERMISSIONS.PRODUCTS_MANAGE), async (req, res, next) => {
    try {
      const q = normalizePersian(req.query.q ?? '');
      const rows = await deps.db.query<any>(
        `SELECT id, code, name, unit FROM products WHERE active = true AND archived_at IS NULL
         AND (normalized_code LIKE $1 OR normalized_name LIKE $1) ORDER BY name LIMIT 20`, [`%${escapeLike(q)}%`]
      );
      res.json(rows.rows);
    } catch (error) { next(error); }
  });
  router.get('/api/requesters/search', requirePermission(PERMISSIONS.REQUESTERS_MANAGE), async (req, res, next) => {
    try {
      const q = normalizePersian(req.query.q ?? '');
      const rows = await deps.db.query<any>(
        `SELECT id, name FROM requesters WHERE active = true AND archived_at IS NULL AND normalized_name LIKE $1 ORDER BY name LIMIT 20`,
        [`%${escapeLike(q)}%`]
      );
      res.json(rows.rows);
    } catch (error) { next(error); }
  });
  return router;
}
