/**
 * OCR 分层处理协调器（plan.md「3.1 分层处理架构」+「3.9 JS 层分层处理」）。
 *
 * Layer 1: 规则匹配（快速，无成本）→ 直接出账单
 * Layer 2: OCR + 正则解析（中等精度，无成本）→ 出账单
 * Layer 3: AI Vision（高精度，有成本）→ 可选，需用户启用
 *
 * 每层失败自动降级到下一层。OCR 引擎与 AI Provider 通过注入抽象。
 */

import { DEDUP_CACHE_MAX, IMAGE_HASH_TRUNCATE_LEN } from '../core/constants';

/** 队列最大长度：超出时丢弃最旧请求，防止 OOM。 */
const _MAX_QUEUE_SIZE = 5;
import { matchOcrRule, parseOcrBill, checkIsDetailPage, detectSuspiciousAmount } from './ocr';
import { hash } from '../core/ledger';
import type { ImportedEvent } from '../core/types';
import { logger } from '../../utils/logger';

/** OCR 引擎抽象（生产用原生 PP-OCRv5 Module，测试用 mock）。 */
export interface OcrEngine {
  /** 识别图片文本，返回纯文本。 */
  recognizeText(imageBase64: string): Promise<string>;
}

/** AI Vision 提供者抽象（Layer 3，可选）。 */
export interface AiVisionProvider {
  /** 识别图片中的账单，返回账单事件或 null。 */
  recognizeBill(imageBase64: string): Promise<ImportedEvent | null>;
}

/** 分层处理结果。 */
export interface OcrProcessResult {
  /** 最终识别出的事件（可能为 null 表示三层都未识别）。 */
  event: ImportedEvent | null;
  /** 命中的层级。 */
  layer: 'layer1-rule' | 'layer2-ocr' | 'layer3-ai' | 'none';
  /** OCR 原始文本（Layer 2 命中时有值）。 */
  ocrText?: string;
  /** 跳过原因（去重/免打扰等）。 */
  skipped?: 'dedup' | 'busy' | 'landscape-dnd' | 'non-bill' | 'ocr-error' | 'ocr-empty';
  /** 可疑金额提示（命中启发式时填充，UI 据此强调确认，避免错误金额入账）。 */
  suspiciousAmount?: { reason: string };
}

export interface OcrProcessorConfig {
  /** 是否启用 Layer 1 规则匹配，默认 true。 */
  layer1Enabled: boolean;
  /** 是否启用 Layer 2 OCR 识别，默认 true。 */
  layer2Enabled: boolean;
  /** 是否启用 Layer 3 AI Vision，默认 false。 */
  layer3Enabled: boolean;
  /** 横屏免打扰模式。 */
  landscapeDnd: boolean;
}

/** 队列中等待处理的请求。 */
interface QueuedRequest {
  imageBase64: string;
  packageName?: string;
  isLandscape: boolean;
  resolve: (result: OcrProcessResult) => void;
}

/**
 * OCR 处理器：协调三层降级 + 去重守卫。
 *
 * 去重守卫参考 AutoAccounting 的 ocrDoing：防止同一截图重复触发。
 * 使用 Promise 队列替代布尔标志，避免快速连续截屏时丢弃合法请求。
 */
export class OcrProcessor {
  private processedHashes = new Map<string, number>(); // hash -> timestamp for LRU
  private processing = false;
  private queue: QueuedRequest[] = [];

  constructor(
    private readonly ocrEngine: OcrEngine,
    private readonly aiProvider?: AiVisionProvider,
    private readonly config: OcrProcessorConfig = { layer1Enabled: true, layer2Enabled: true, layer3Enabled: false, landscapeDnd: false },
  ) {}

  /**
   * 处理一张截图/图片，返回账单事件。
   * 串行化：同时只处理一张（队列机制）。
   */
  process(
    imageBase64: string,
    packageName?: string,
    isLandscape = false,
  ): Promise<OcrProcessResult> {
    return new Promise(resolve => {
      this.queue.push({ imageBase64, packageName, isLandscape, resolve });
      this.processQueue();
    });
  }

  private async processQueue(): Promise<void> {
    if (this.processing || this.queue.length === 0) return;
    this.processing = true;
    const request = this.queue.shift()!;
    
    try {
      const result = await this.doProcess(request.imageBase64, request.packageName, request.isLandscape);
      request.resolve(result);
    } catch (e) {
      logger.error('ocrProcessor', '[队列异常] 处理队列包含未捕获异常', e);
      request.resolve({ event: null, layer: 'none', skipped: 'ocr-error' });
    } finally {
      this.processing = false;
      this.processQueue();
    }
  }

  private async doProcess(
    imageBase64: string,
    packageName?: string,
    isLandscape = false,
  ): Promise<OcrProcessResult> {
    const startTime = Date.now();

    // 横屏免打扰
    if (this.config.landscapeDnd && isLandscape) {
      logger.debug('ocrProcessor', `[跳过] 横屏免打扰模式`);
      return { event: null, layer: 'none', skipped: 'landscape-dnd' };
    }

    // 去重守卫：同一图片不重复处理
    const imageHash = hash(imageBase64.slice(0, IMAGE_HASH_TRUNCATE_LEN));
    if (this.processedHashes.has(imageHash)) {
      // LRU: 更新时间戳
      this.processedHashes.set(imageHash, Date.now());
      logger.debug('ocrProcessor', `[跳过] 图片 hash 去重`);
      return { event: null, layer: 'none', skipped: 'dedup' };
    }

    try {
      let ocrText = '';

      // L1 和 L2 都需要 OCR 文本：如果二者都关了，跳过 OCR 调用
      if (this.config.layer1Enabled || this.config.layer2Enabled) {
        const ocrStartTime = Date.now();
        ocrText = await this.ocrEngine.recognizeText(imageBase64);
        const ocrDuration = Date.now() - ocrStartTime;
        logger.debug('ocrProcessor', `Native OCR 引擎识别完成 (耗时: ${ocrDuration}ms, 字符数: ${ocrText.length})`);
        // 诊断：OCR 返回大量字符但全是空白时，提示原图可能截到空白帧（与原生截图诊断交叉验证）
        if (ocrText.length > 50 && ocrText.trim().length === 0) {
          const nonWhitespace = ocrText.replace(/\s/g, '');
          const firstCharCode = ocrText.charCodeAt(0);
          logger.warn('ocrProcessor', `[OCR空白诊断] 返回 ${ocrText.length} 字符但全是空白（非空白字符数=${nonWhitespace.length}，首字符charCode=${firstCharCode}）。原图可能截到 WebView 未渲染的空白帧，或被反截屏。`);
        }

        // OCR 成功后才标记已处理，避免异常时永久阻断重试
        this.processedHashes.set(imageHash, Date.now());
        // LRU 淘汰：超出阈值 20% 时批量清理最旧条目
        if (this.processedHashes.size > DEDUP_CACHE_MAX * 1.2) {
          const entries = [...this.processedHashes.entries()]
            .sort((a, b) => a[1] - b[1]);
          const toDelete = entries.slice(0, entries.length - DEDUP_CACHE_MAX);
          for (const [key] of toDelete) {
            this.processedHashes.delete(key);
          }
        }

        if (!ocrText || !ocrText.trim()) {
          // 无文本，尝试 Layer 3
          if (this.config.layer3Enabled) {
            return await this.tryLayer3(imageBase64, undefined);
          }
          // OCR 引擎返回空文本（与"有内容但不是账单"区分，便于日志排查）
          return { event: null, layer: 'none', skipped: 'ocr-empty' };
        }
      } else {
        // L1 和 L2 都关闭，仍标记 hash 避免重复处理
        this.processedHashes.set(imageHash, Date.now());
      }

      // Layer 1: 规则匹配（仅当启用，且非详情页以防火误提取）
      if (this.config.layer1Enabled && ocrText) {
        const isDetailPage = checkIsDetailPage(ocrText);
        if (!isDetailPage) {
          const ruleMatch = matchOcrRule(ocrText, packageName);
          if (ruleMatch.matched && ruleMatch.event) {
            const totalDuration = Date.now() - startTime;
            logger.info('ocrProcessor', `[Layer 1] 规则匹配成功 (总耗时: ${totalDuration}ms): 金额 ${ruleMatch.event.amount} ${ruleMatch.event.currency}, 对手: ${ruleMatch.event.counterparty || '无'}`);
            const suspicious = detectSuspiciousAmount(ruleMatch.event.amount, ocrText);
            if (suspicious) {
              logger.warn('ocrProcessor', `[可疑金额] ${suspicious.reason}`);
              ruleMatch.event.raw['suspiciousAmount'] = suspicious.reason;
            }
            return { event: ruleMatch.event, layer: 'layer1-rule', ocrText, suspiciousAmount: suspicious ?? undefined };
          }
        }
      }

      // Layer 2: OCR 解析（仅当启用）
      if (this.config.layer2Enabled && ocrText) {
        const fallback = parseOcrBill(ocrText, packageName);
        if (fallback) {
          const totalDuration = Date.now() - startTime;
          logger.info('ocrProcessor', `[Layer 2] OCR 正则解析成功 (总耗时: ${totalDuration}ms): 金额 ${fallback.amount} ${fallback.currency}, 对手: ${fallback.counterparty || '无'}`);
          const suspicious = detectSuspiciousAmount(fallback.amount, ocrText);
          if (suspicious) {
            logger.warn('ocrProcessor', `[可疑金额] ${suspicious.reason}`);
            fallback.raw['suspiciousAmount'] = suspicious.reason;
          }
          return { event: fallback, layer: 'layer2-ocr', ocrText, suspiciousAmount: suspicious ?? undefined };
        }
      }

      // Layer 3: AI Vision（仅当启用）
      if (this.config.layer3Enabled) {
        return await this.tryLayer3(imageBase64, ocrText || undefined);
      }

      // 全部未命中或全部关闭
      return { event: null, layer: 'none', ocrText, skipped: 'non-bill' };
    } catch (e) {
      logger.error('ocrProcessor', '[处理异常] doProcess 执行异常', e);
      return { event: null, layer: 'none', skipped: 'ocr-error' };
    }
  }

  private async tryLayer3(imageBase64: string, ocrText?: string): Promise<OcrProcessResult> {
    if (!this.config.layer3Enabled || !this.aiProvider) {
      return { event: null, layer: 'none', ocrText, skipped: 'non-bill' };
    }
    const startTime = Date.now();
    try {
      const event = await this.aiProvider.recognizeBill(imageBase64);
      const duration = Date.now() - startTime;
      if (event) {
        logger.info('ocrProcessor', `[Layer 3] AI Vision 识别成功 (耗时: ${duration}ms): 金额 ${event.amount} ${event.currency}, 对方: ${event.counterparty || '无'}`);
        return { event, layer: 'layer3-ai', ocrText };
      }
      logger.info('ocrProcessor', `[Layer 3] AI Vision 未能识别为账单 (耗时: ${duration}ms)`);
      return { event: null, layer: 'none', ocrText, skipped: 'non-bill' };
    } catch (e) {
      logger.error('ocrProcessor', '[Layer 3] AI Vision 调用异常', e);
      return { event: null, layer: 'none', ocrText, skipped: 'non-bill' };
    }
  }

  /** 重置去重状态（切换页面/应用重启时）。 */
  reset(): void {
    this.processedHashes.clear();
    this.processing = false;
    this.queue = [];
  }
}

/** 模拟 OCR 引擎（测试用，返回预设文本）。 */
export class MockOcrEngine implements OcrEngine {
  constructor(private responses: Map<string, string> = new Map(), public defaultResponse = '') {}
  async recognizeText(imageBase64: string): Promise<string> {
    // 简单匹配：按图片 hash 前缀找预设
    const h = hash(imageBase64.slice(0, 100));
    return this.responses.get(h) ?? this.defaultResponse;
  }
  /** 测试辅助：注入预设响应。 */
  setResponse(imageBase64: string, text: string): void {
    this.responses.set(hash(imageBase64.slice(0, 100)), text);
  }
}

/** 模拟 AI Vision Provider（测试用）。 */
export class MockAiVisionProvider implements AiVisionProvider {
  constructor(private response: ImportedEvent | null = null) {}
  async recognizeBill(): Promise<ImportedEvent | null> {
    return this.response;
  }
}

