// src/services/db/receipt.db.ts
import type { Pool, PoolClient } from 'pg';
import { getPool } from './connection.db';
import { NotFoundError, handleDbError } from './errors.db';
import type { Logger } from 'pino';
import type {
  ReceiptStatus,
  ReceiptItemStatus,
  ReceiptProcessingStep,
  ReceiptProcessingStatus,
  OcrProvider,
  ReceiptScan,
  ReceiptItem,
  ReceiptProcessingLogRecord,
} from '../../types/expiry';

/**
 * Database row type for receipts table.
 */
interface ReceiptRow {
  receipt_id: number;
  user_id: string;
  store_id: number | null;
  receipt_image_url: string;
  transaction_date: string | null;
  total_amount_cents: number | null;
  status: ReceiptStatus;
  raw_text: string | null;
  store_confidence: number | null;
  ocr_provider: OcrProvider | null;
  error_details: string | null;
  retry_count: number;
  ocr_confidence: number | null;
  currency: string;
  created_at: string;
  processed_at: string | null;
  updated_at: string;
}

/**
 * Database row type for receipt_items table.
 */
interface ReceiptItemRow {
  receipt_item_id: number;
  receipt_id: number;
  raw_item_description: string;
  quantity: number;
  price_paid_cents: number;
  master_item_id: number | null;
  product_id: number | null;
  status: ReceiptItemStatus;
  line_number: number | null;
  match_confidence: number | null;
  is_discount: boolean;
  unit_price_cents: number | null;
  unit_type: string | null;
  added_to_pantry: boolean;
  pantry_item_id: number | null;
  upc_code: string | null;
  created_at: string;
  updated_at: string;
}

/**
 * Database row type for store_receipt_patterns table.
 */
interface StoreReceiptPatternRow {
  pattern_id: number;
  store_id: number;
  pattern_type: string;
  pattern_value: string;
  priority: number;
  is_active: boolean;
  created_at: string;
  updated_at: string;
}

/**
 * Request to create a new receipt scan.
 */
export interface CreateReceiptRequest {
  user_id: string;
  receipt_image_url: string;
  store_id?: number;
  transaction_date?: string;
}

/**
 * Request to update receipt processing status.
 */
export interface UpdateReceiptStatusRequest {
  status?: ReceiptStatus;
  raw_text?: string;
  store_id?: number;
  store_confidence?: number;
  total_amount_cents?: number;
  transaction_date?: string;
  ocr_provider?: OcrProvider;
  ocr_confidence?: number;
  error_details?: Record<string, unknown>;
  processed_at?: string;
}

/**
 * Request to add a receipt item.
 */
export interface AddReceiptItemRequest {
  receipt_id: number;
  raw_item_description: string;
  quantity?: number;
  price_paid_cents: number;
  line_number?: number;
  is_discount?: boolean;
  unit_price_cents?: number;
  unit_type?: string;
  upc_code?: string;
}

/**
 * Request to update a receipt item.
 */
export interface UpdateReceiptItemRequest {
  status?: ReceiptItemStatus;
  master_item_id?: number | null;
  product_id?: number | null;
  match_confidence?: number;
  added_to_pantry?: boolean;
  pantry_item_id?: number | null;
}

/**
 * Options for querying receipts.
 */
export interface ReceiptQueryOptions {
  user_id: string;
  status?: ReceiptStatus;
  store_id?: number;
  from_date?: string;
  to_date?: string;
  limit?: number;
  offset?: number;
}

/**
 * Repository for receipt scanning database operations.
 * Handles receipts, receipt items, processing logs, and store patterns.
 */
export class ReceiptRepository {
  private db: Pick<Pool | PoolClient, 'query'>;

  constructor(db: Pick<Pool | PoolClient, 'query'> = getPool()) {
    this.db = db;
  }

  // ============================================================================
  // RECEIPTS
  // ============================================================================

  /**
   * Creates a new receipt scan record.
   */
  async createReceipt(request: CreateReceiptRequest, logger: Logger): Promise<ReceiptScan> {
    try {
      logger.debug({ request }, 'Creating new receipt record');

      const res = await this.db.query<ReceiptRow>(
        `INSERT INTO public.receipts
         (user_id, receipt_image_url, store_id, transaction_date, status)
         VALUES ($1, $2, $3, $4, 'pending')
         RETURNING *`,
        [
          request.user_id,
          request.receipt_image_url,
          request.store_id || null,
          request.transaction_date || null,
        ],
      );

      logger.info({ receiptId: res.rows[0].receipt_id }, 'Receipt record created');
      return this.mapReceiptRowToReceipt(res.rows[0]);
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in createReceipt',
        { request },
        {
          fkMessage: 'The specified store does not exist.',
          defaultMessage: 'Failed to create receipt record.',
        },
      );
    }
  }

  /**
   * Gets a receipt by ID.
   */
  async getReceiptById(receiptId: number, userId: string, logger: Logger): Promise<ReceiptScan> {
    try {
      const res = await this.db.query<ReceiptRow>(
        `SELECT * FROM public.receipts WHERE receipt_id = $1 AND user_id = $2`,
        [receiptId, userId],
      );

      if (res.rowCount === 0) {
        throw new NotFoundError('Receipt not found.');
      }

      return this.mapReceiptRowToReceipt(res.rows[0]);
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getReceiptById',
        { receiptId, userId },
        {
          defaultMessage: 'Failed to get receipt.',
        },
      );
    }
  }

  /**
   * Gets receipts for a user with optional filtering.
   */
  async getReceipts(
    options: ReceiptQueryOptions,
    logger: Logger,
  ): Promise<{ receipts: ReceiptScan[]; total: number }> {
    const { user_id, status, store_id, from_date, to_date, limit = 50, offset = 0 } = options;

    try {
      // Build dynamic WHERE clause
      const conditions: string[] = ['user_id = $1'];
      const params: (string | number)[] = [user_id];
      let paramIndex = 2;

      if (status) {
        conditions.push(`status = $${paramIndex++}`);
        params.push(status);
      }

      if (store_id) {
        conditions.push(`store_id = $${paramIndex++}`);
        params.push(store_id);
      }

      if (from_date) {
        conditions.push(`created_at >= $${paramIndex++}`);
        params.push(from_date);
      }

      if (to_date) {
        conditions.push(`created_at <= $${paramIndex++}`);
        params.push(to_date);
      }

      const whereClause = conditions.join(' AND ');

      // Get total count
      const countRes = await this.db.query<{ count: string }>(
        `SELECT COUNT(*) FROM public.receipts WHERE ${whereClause}`,
        params,
      );
      const total = parseInt(countRes.rows[0].count, 10);

      // Get paginated results
      const dataParams = [...params, limit, offset];
      const dataRes = await this.db.query<ReceiptRow>(
        `SELECT * FROM public.receipts
         WHERE ${whereClause}
         ORDER BY created_at DESC
         LIMIT $${paramIndex++} OFFSET $${paramIndex}`,
        dataParams,
      );

      return {
        receipts: dataRes.rows.map((row) => this.mapReceiptRowToReceipt(row)),
        total,
      };
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getReceipts',
        { options },
        {
          defaultMessage: 'Failed to get receipts.',
        },
      );
    }
  }

  /**
   * Updates a receipt's processing status and data.
   */
  async updateReceipt(
    receiptId: number,
    updates: UpdateReceiptStatusRequest,
    logger: Logger,
  ): Promise<ReceiptScan> {
    try {
      logger.debug({ receiptId, updates }, 'Updating receipt');

      // Build dynamic SET clause
      const setClauses: string[] = ['updated_at = NOW()'];
      const values: (string | number | null)[] = [];
      let paramIndex = 1;

      if (updates.status !== undefined) {
        setClauses.push(`status = $${paramIndex++}`);
        values.push(updates.status);
      }

      if (updates.raw_text !== undefined) {
        setClauses.push(`raw_text = $${paramIndex++}`);
        values.push(updates.raw_text);
      }

      if (updates.store_id !== undefined) {
        setClauses.push(`store_id = $${paramIndex++}`);
        values.push(updates.store_id);
      }

      if (updates.store_confidence !== undefined) {
        setClauses.push(`store_confidence = $${paramIndex++}`);
        values.push(updates.store_confidence);
      }

      if (updates.total_amount_cents !== undefined) {
        setClauses.push(`total_amount_cents = $${paramIndex++}`);
        values.push(updates.total_amount_cents);
      }

      if (updates.transaction_date !== undefined) {
        setClauses.push(`transaction_date = $${paramIndex++}`);
        values.push(updates.transaction_date);
      }

      if (updates.ocr_provider !== undefined) {
        setClauses.push(`ocr_provider = $${paramIndex++}`);
        values.push(updates.ocr_provider);
      }

      if (updates.ocr_confidence !== undefined) {
        setClauses.push(`ocr_confidence = $${paramIndex++}`);
        values.push(updates.ocr_confidence);
      }

      if (updates.error_details !== undefined) {
        setClauses.push(`error_details = $${paramIndex++}`);
        values.push(JSON.stringify(updates.error_details));
      }

      if (updates.processed_at !== undefined) {
        setClauses.push(`processed_at = $${paramIndex++}`);
        values.push(updates.processed_at);
      }

      values.push(receiptId);

      const res = await this.db.query<ReceiptRow>(
        `UPDATE public.receipts SET ${setClauses.join(', ')} WHERE receipt_id = $${paramIndex} RETURNING *`,
        values,
      );

      if (res.rowCount === 0) {
        throw new NotFoundError('Receipt not found.');
      }

      logger.info({ receiptId, status: res.rows[0].status }, 'Receipt updated');
      return this.mapReceiptRowToReceipt(res.rows[0]);
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in updateReceipt',
        { receiptId, updates },
        {
          defaultMessage: 'Failed to update receipt.',
        },
      );
    }
  }

  /**
   * Increments the retry count for a failed receipt.
   */
  async incrementRetryCount(receiptId: number, logger: Logger): Promise<number> {
    try {
      const res = await this.db.query<{ retry_count: number }>(
        `UPDATE public.receipts
         SET retry_count = retry_count + 1, updated_at = NOW()
         WHERE receipt_id = $1
         RETURNING retry_count`,
        [receiptId],
      );

      if (res.rowCount === 0) {
        throw new NotFoundError('Receipt not found.');
      }

      logger.debug({ receiptId, retryCount: res.rows[0].retry_count }, 'Retry count incremented');
      return res.rows[0].retry_count;
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in incrementRetryCount',
        { receiptId },
        {
          defaultMessage: 'Failed to increment retry count.',
        },
      );
    }
  }

  /**
   * Gets receipts that need processing (pending or failed with retries remaining).
   */
  async getReceiptsNeedingProcessing(
    maxRetries: number,
    limit: number,
    logger: Logger,
  ): Promise<ReceiptScan[]> {
    try {
      const res = await this.db.query<ReceiptRow>(
        `SELECT * FROM public.receipts
         WHERE (status = 'pending' OR (status = 'failed' AND retry_count < $1))
         ORDER BY created_at ASC
         LIMIT $2`,
        [maxRetries, limit],
      );

      logger.debug({ count: res.rowCount }, 'Fetched receipts needing processing');
      return res.rows.map((row) => this.mapReceiptRowToReceipt(row));
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getReceiptsNeedingProcessing',
        { maxRetries, limit },
        {
          defaultMessage: 'Failed to get receipts needing processing.',
        },
      );
    }
  }

  /**
   * Deletes a receipt and all associated items.
   */
  async deleteReceipt(receiptId: number, userId: string, logger: Logger): Promise<void> {
    try {
      const res = await this.db.query(
        `DELETE FROM public.receipts WHERE receipt_id = $1 AND user_id = $2`,
        [receiptId, userId],
      );

      if (res.rowCount === 0) {
        throw new NotFoundError('Receipt not found or user does not have permission.');
      }

      logger.info({ receiptId }, 'Receipt deleted');
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in deleteReceipt',
        { receiptId, userId },
        {
          defaultMessage: 'Failed to delete receipt.',
        },
      );
    }
  }

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

  /**
   * Adds items extracted from a receipt.
   */
  async addReceiptItems(items: AddReceiptItemRequest[], logger: Logger): Promise<ReceiptItem[]> {
    if (items.length === 0) {
      return [];
    }

    try {
      logger.debug({ count: items.length, receiptId: items[0].receipt_id }, 'Adding receipt items');

      // Build batch insert
      const values: (string | number | boolean | null)[] = [];
      const valuePlaceholders: string[] = [];
      let paramIndex = 1;

      for (const item of items) {
        valuePlaceholders.push(
          `($${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++})`,
        );
        values.push(
          item.receipt_id,
          item.raw_item_description,
          item.quantity ?? 1,
          item.price_paid_cents,
          item.line_number ?? null,
          item.is_discount ?? false,
          item.unit_price_cents ?? null,
          item.unit_type ?? null,
          item.upc_code ?? null,
        );
      }

      const res = await this.db.query<ReceiptItemRow>(
        `INSERT INTO public.receipt_items
         (receipt_id, raw_item_description, quantity, price_paid_cents, line_number, is_discount, unit_price_cents, unit_type, upc_code)
         VALUES ${valuePlaceholders.join(', ')}
         RETURNING *`,
        values,
      );

      logger.info({ count: res.rowCount, receiptId: items[0].receipt_id }, 'Receipt items added');
      return res.rows.map((row) => this.mapReceiptItemRowToReceiptItem(row));
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in addReceiptItems',
        { itemCount: items.length },
        {
          fkMessage: 'The specified receipt does not exist.',
          defaultMessage: 'Failed to add receipt items.',
        },
      );
    }
  }

  /**
   * Gets all items for a receipt.
   */
  async getReceiptItems(receiptId: number, logger: Logger): Promise<ReceiptItem[]> {
    try {
      const res = await this.db.query<ReceiptItemRow>(
        `SELECT * FROM public.receipt_items
         WHERE receipt_id = $1
         ORDER BY line_number ASC NULLS LAST, receipt_item_id ASC`,
        [receiptId],
      );

      return res.rows.map((row) => this.mapReceiptItemRowToReceiptItem(row));
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getReceiptItems',
        { receiptId },
        {
          defaultMessage: 'Failed to get receipt items.',
        },
      );
    }
  }

  /**
   * Updates a receipt item (matching, pantry linking, etc.).
   */
  async updateReceiptItem(
    receiptItemId: number,
    updates: UpdateReceiptItemRequest,
    logger: Logger,
  ): Promise<ReceiptItem> {
    try {
      const setClauses: string[] = ['updated_at = NOW()'];
      const values: (string | number | boolean | null)[] = [];
      let paramIndex = 1;

      if (updates.status !== undefined) {
        setClauses.push(`status = $${paramIndex++}`);
        values.push(updates.status);
      }

      if (updates.master_item_id !== undefined) {
        setClauses.push(`master_item_id = $${paramIndex++}`);
        values.push(updates.master_item_id);
      }

      if (updates.product_id !== undefined) {
        setClauses.push(`product_id = $${paramIndex++}`);
        values.push(updates.product_id);
      }

      if (updates.match_confidence !== undefined) {
        setClauses.push(`match_confidence = $${paramIndex++}`);
        values.push(updates.match_confidence);
      }

      if (updates.added_to_pantry !== undefined) {
        setClauses.push(`added_to_pantry = $${paramIndex++}`);
        values.push(updates.added_to_pantry);
      }

      if (updates.pantry_item_id !== undefined) {
        setClauses.push(`pantry_item_id = $${paramIndex++}`);
        values.push(updates.pantry_item_id);
      }

      values.push(receiptItemId);

      const res = await this.db.query<ReceiptItemRow>(
        `UPDATE public.receipt_items SET ${setClauses.join(', ')} WHERE receipt_item_id = $${paramIndex} RETURNING *`,
        values,
      );

      if (res.rowCount === 0) {
        throw new NotFoundError('Receipt item not found.');
      }

      return this.mapReceiptItemRowToReceiptItem(res.rows[0]);
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in updateReceiptItem',
        { receiptItemId, updates },
        {
          defaultMessage: 'Failed to update receipt item.',
        },
      );
    }
  }

  /**
   * Gets receipt items that haven't been added to pantry.
   */
  async getUnaddedReceiptItems(receiptId: number, logger: Logger): Promise<ReceiptItem[]> {
    try {
      const res = await this.db.query<ReceiptItemRow>(
        `SELECT * FROM public.receipt_items
         WHERE receipt_id = $1
           AND added_to_pantry = false
           AND is_discount = false
         ORDER BY line_number ASC NULLS LAST`,
        [receiptId],
      );

      return res.rows.map((row) => this.mapReceiptItemRowToReceiptItem(row));
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getUnaddedReceiptItems',
        { receiptId },
        {
          defaultMessage: 'Failed to get unadded receipt items.',
        },
      );
    }
  }

  // ============================================================================
  // PROCESSING LOG
  // ============================================================================

  /**
   * Logs a processing step for a receipt.
   */
  async logProcessingStep(
    receiptId: number,
    step: ReceiptProcessingStep,
    status: ReceiptProcessingStatus,
    logger: Logger,
    options: {
      provider?: OcrProvider;
      durationMs?: number;
      tokensUsed?: number;
      costCents?: number;
      inputData?: Record<string, unknown>;
      outputData?: Record<string, unknown>;
      errorMessage?: string;
    } = {},
  ): Promise<ReceiptProcessingLogRecord> {
    try {
      logger.debug({ receiptId, step, status }, 'Logging processing step');

      const res = await this.db.query<ReceiptProcessingLogRecord>(
        `INSERT INTO public.receipt_processing_log
         (receipt_id, processing_step, status, provider, duration_ms, tokens_used, cost_cents, input_data, output_data, error_message)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
         RETURNING *`,
        [
          receiptId,
          step,
          status,
          options.provider || null,
          options.durationMs ?? null,
          options.tokensUsed ?? null,
          options.costCents ?? null,
          options.inputData ? JSON.stringify(options.inputData) : null,
          options.outputData ? JSON.stringify(options.outputData) : null,
          options.errorMessage || null,
        ],
      );

      return res.rows[0];
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in logProcessingStep',
        { receiptId, step, status },
        {
          defaultMessage: 'Failed to log processing step.',
        },
      );
    }
  }

  /**
   * Gets processing logs for a receipt.
   */
  async getProcessingLogs(
    receiptId: number,
    logger: Logger,
  ): Promise<ReceiptProcessingLogRecord[]> {
    try {
      const res = await this.db.query<ReceiptProcessingLogRecord>(
        `SELECT * FROM public.receipt_processing_log
         WHERE receipt_id = $1
         ORDER BY created_at ASC`,
        [receiptId],
      );

      return res.rows;
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getProcessingLogs',
        { receiptId },
        {
          defaultMessage: 'Failed to get processing logs.',
        },
      );
    }
  }

  /**
   * Gets processing statistics for monitoring.
   */
  async getProcessingStats(
    logger: Logger,
    options: { fromDate?: string; toDate?: string } = {},
  ): Promise<{
    total_receipts: number;
    completed: number;
    failed: number;
    pending: number;
    avg_processing_time_ms: number;
    total_cost_cents: number;
  }> {
    try {
      const conditions: string[] = [];
      const params: string[] = [];
      let paramIndex = 1;

      if (options.fromDate) {
        conditions.push(`created_at >= $${paramIndex++}`);
        params.push(options.fromDate);
      }

      if (options.toDate) {
        conditions.push(`created_at <= $${paramIndex++}`);
        params.push(options.toDate);
      }

      const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';

      const receiptStatsRes = await this.db.query<{
        total_receipts: string;
        completed: string;
        failed: string;
        pending: string;
      }>(
        `SELECT
          COUNT(*) AS total_receipts,
          COUNT(*) FILTER (WHERE status = 'completed') AS completed,
          COUNT(*) FILTER (WHERE status = 'failed') AS failed,
          COUNT(*) FILTER (WHERE status = 'pending') AS pending
         FROM public.receipts ${whereClause}`,
        params,
      );

      const processingStatsRes = await this.db.query<{
        avg_duration_ms: string;
        total_cost_cents: string;
      }>(
        `SELECT
          COALESCE(AVG(duration_ms), 0) AS avg_duration_ms,
          COALESCE(SUM(cost_cents), 0) AS total_cost_cents
         FROM public.receipt_processing_log ${whereClause}`,
        params,
      );

      const receiptStats = receiptStatsRes.rows[0];
      const processingStats = processingStatsRes.rows[0];

      return {
        total_receipts: parseInt(receiptStats.total_receipts, 10),
        completed: parseInt(receiptStats.completed, 10),
        failed: parseInt(receiptStats.failed, 10),
        pending: parseInt(receiptStats.pending, 10),
        avg_processing_time_ms: parseFloat(processingStats.avg_duration_ms),
        total_cost_cents: parseInt(processingStats.total_cost_cents, 10),
      };
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getProcessingStats',
        { options },
        {
          defaultMessage: 'Failed to get processing statistics.',
        },
      );
    }
  }

  // ============================================================================
  // STORE RECEIPT PATTERNS
  // ============================================================================

  /**
   * Gets all active patterns for store detection.
   */
  async getActiveStorePatterns(logger: Logger): Promise<StoreReceiptPatternRow[]> {
    try {
      const res = await this.db.query<StoreReceiptPatternRow>(
        `SELECT * FROM public.store_receipt_patterns
         WHERE is_active = true
         ORDER BY priority DESC, pattern_type`,
      );

      return res.rows;
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in getActiveStorePatterns',
        {},
        {
          defaultMessage: 'Failed to get store patterns.',
        },
      );
    }
  }

  /**
   * Adds a new store receipt pattern.
   */
  async addStorePattern(
    storeId: number,
    patternType: string,
    patternValue: string,
    logger: Logger,
    options: { priority?: number } = {},
  ): Promise<StoreReceiptPatternRow> {
    try {
      const res = await this.db.query<StoreReceiptPatternRow>(
        `INSERT INTO public.store_receipt_patterns
         (store_id, pattern_type, pattern_value, priority)
         VALUES ($1, $2, $3, $4)
         RETURNING *`,
        [storeId, patternType, patternValue, options.priority ?? 0],
      );

      logger.info({ storeId, patternType }, 'Store pattern added');
      return res.rows[0];
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in addStorePattern',
        { storeId, patternType, patternValue },
        {
          fkMessage: 'The specified store does not exist.',
          uniqueMessage: 'This pattern already exists for this store.',
          checkMessage: 'Invalid pattern type or empty pattern value.',
          defaultMessage: 'Failed to add store pattern.',
        },
      );
    }
  }

  /**
   * Deactivates a store pattern.
   */
  async deactivateStorePattern(patternId: number, logger: Logger): Promise<void> {
    try {
      const res = await this.db.query(
        `UPDATE public.store_receipt_patterns
         SET is_active = false, updated_at = NOW()
         WHERE pattern_id = $1`,
        [patternId],
      );

      if (res.rowCount === 0) {
        throw new NotFoundError('Pattern not found.');
      }

      logger.info({ patternId }, 'Store pattern deactivated');
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in deactivateStorePattern',
        { patternId },
        {
          defaultMessage: 'Failed to deactivate store pattern.',
        },
      );
    }
  }

  /**
   * Detects store from receipt text using patterns.
   */
  async detectStoreFromText(
    receiptText: string,
    logger: Logger,
  ): Promise<{ store_id: number; confidence: number } | null> {
    try {
      logger.debug('Attempting to detect store from receipt text');

      const patterns = await this.getActiveStorePatterns(logger);

      // Group patterns by store
      const storePatternMap = new Map<number, StoreReceiptPatternRow[]>();
      for (const pattern of patterns) {
        const existing = storePatternMap.get(pattern.store_id) || [];
        existing.push(pattern);
        storePatternMap.set(pattern.store_id, existing);
      }

      // Try to match each store's patterns
      let bestMatch: { store_id: number; confidence: number; matchCount: number } | null = null;

      for (const [storeId, storePatterns] of storePatternMap) {
        let matchCount = 0;
        let totalPriority = 0;

        for (const pattern of storePatterns) {
          try {
            let matches = false;

            if (
              pattern.pattern_type === 'header_regex' ||
              pattern.pattern_type === 'footer_regex'
            ) {
              const regex = new RegExp(pattern.pattern_value, 'i');
              matches = regex.test(receiptText);
            } else {
              // Literal text match for phone_number, address_fragment, etc.
              matches = receiptText.toLowerCase().includes(pattern.pattern_value.toLowerCase());
            }

            if (matches) {
              matchCount++;
              totalPriority += pattern.priority;
            }
          } catch (regexError) {
            // Invalid regex pattern, skip it
            logger.warn(
              { patternId: pattern.pattern_id, error: regexError },
              'Invalid regex pattern',
            );
          }
        }

        if (matchCount > 0) {
          // Calculate confidence based on match count and priority
          const confidence = Math.min(
            1,
            (matchCount / storePatterns.length) * 0.7 + (totalPriority / 100) * 0.3,
          );

          if (
            !bestMatch ||
            matchCount > bestMatch.matchCount ||
            (matchCount === bestMatch.matchCount && confidence > bestMatch.confidence)
          ) {
            bestMatch = { store_id: storeId, confidence, matchCount };
          }
        }
      }

      if (bestMatch) {
        logger.info(
          { storeId: bestMatch.store_id, confidence: bestMatch.confidence },
          'Store detected from receipt text',
        );
        return { store_id: bestMatch.store_id, confidence: bestMatch.confidence };
      }

      logger.debug('No store match found from receipt text');
      return null;
    } catch (error) {
      handleDbError(
        error,
        logger,
        'Database error in detectStoreFromText',
        {},
        {
          defaultMessage: 'Failed to detect store from receipt text.',
        },
      );
    }
  }

  // ============================================================================
  // HELPER METHODS
  // ============================================================================

  /**
   * Maps a receipt database row to ReceiptScan type.
   */
  private mapReceiptRowToReceipt(row: ReceiptRow): ReceiptScan {
    return {
      receipt_id: row.receipt_id,
      user_id: row.user_id,
      store_id: row.store_id,
      receipt_image_url: row.receipt_image_url,
      transaction_date: row.transaction_date,
      total_amount_cents: row.total_amount_cents,
      status: row.status,
      raw_text: row.raw_text,
      store_confidence: row.store_confidence !== null ? Number(row.store_confidence) : null,
      ocr_provider: row.ocr_provider,
      error_details: row.error_details ? JSON.parse(row.error_details) : null,
      retry_count: row.retry_count,
      ocr_confidence: row.ocr_confidence !== null ? Number(row.ocr_confidence) : null,
      currency: row.currency,
      created_at: row.created_at,
      processed_at: row.processed_at,
      updated_at: row.updated_at,
    };
  }

  /**
   * Maps a receipt item database row to ReceiptItem type.
   */
  private mapReceiptItemRowToReceiptItem(row: ReceiptItemRow): ReceiptItem {
    return {
      receipt_item_id: row.receipt_item_id,
      receipt_id: row.receipt_id,
      raw_item_description: row.raw_item_description,
      quantity: Number(row.quantity),
      price_paid_cents: row.price_paid_cents,
      master_item_id: row.master_item_id,
      product_id: row.product_id,
      status: row.status,
      line_number: row.line_number,
      match_confidence: row.match_confidence !== null ? Number(row.match_confidence) : null,
      is_discount: row.is_discount,
      unit_price_cents: row.unit_price_cents,
      unit_type: row.unit_type,
      added_to_pantry: row.added_to_pantry,
      pantry_item_id: row.pantry_item_id,
      upc_code: row.upc_code,
      created_at: row.created_at,
      updated_at: row.updated_at,
    };
  }
}
