/**
 * 通知监听（plan.md「4.1 通知监听服务」）。
 *
 * 参考 AutoAccounting 的 NotificationListenerService（含 MD5 去重 + 关键词黑白名单）。
 * 原生监听通过 Config Plugin 注入，此处定义桥接 + 解析器（纯函数，可单测）。
 */

import { PAYMENT_PACKAGES } from '../domain/core/constants';
import { hash } from '../domain/core/ledger';
import { passesKeywordFilter, type KeywordFilter } from '../domain/rules/keywordFilter';
import { matchOcrRule } from '../domain/pipeline/ocr';
import type { ImportedEvent } from '../domain/core/types';
import { logger } from '../utils/logger';

/** 通知事件（原生监听器推送）。 */
export interface NotificationEvent {
  packageName: string;
  title: string;
  text: string;
  timestamp: number;
}

/** 原生通知监听器契约（Config Plugin 注入）。 */
export interface NotificationListener {
  start(handler: (event: NotificationEvent) => void): Promise<void>;
  stop(): Promise<void>;
  /** 请求重新绑定（Android Doze 后断开重连，参考 AutoAccounting requestRebind）。 */
  requestRebind(): Promise<void>;
  /** 白名单包名（支付类 App）。 */
  setWhitelist(packages: string[]): Promise<void>;
}

/** 默认支付 App 白名单（数据源见 constants.ts PAYMENT_PACKAGES）。 */
export const DEFAULT_PAYMENT_PACKAGES = Object.keys(PAYMENT_PACKAGES);

/**
 * 通知解析器（plan.md「4.1」）：从通知 title+text 提取账单事件。
 * 复用 OCR 的规则匹配逻辑（微信/支付宝/银行卡正则）。
 */
export function parseNotification(event: NotificationEvent): ImportedEvent | null {
  const text = `${event.title} ${event.text}`;
  // 复用 OCR 规则匹配（微信/支付宝/银行卡正则）
  const match = matchOcrRule(text, event.packageName);
  return match.matched ? match.event : null;
}

/**
 * 通知通道协调器：白名单过滤 + 关键词过滤 + MD5 去重 + 解析。
 */
export class NotificationChannel {
  private md5Hashes = new Set<string>();
  private handler: ((event: ImportedEvent, raw: NotificationEvent) => void) | null = null;

  constructor(
    private readonly nativeListener: NotificationListener | null = null,
    private whitelist: string[] = DEFAULT_PAYMENT_PACKAGES,
    private keywordFilter: KeywordFilter | null = null,
  ) {}

  /** 注册账单事件回调。 */
  onBillDetected(handler: (event: ImportedEvent, raw: NotificationEvent) => void): void {
    this.handler = handler;
  }

  /** 启动监听。 */
  async start(): Promise<void> {
    if (!this.nativeListener) return;
    await this.nativeListener.setWhitelist(this.whitelist);
    await this.nativeListener.start(e => this.handleNotification(e));
  }

  async stop(): Promise<void> {
    await this.nativeListener?.stop();
  }

  /** 处理单个通知事件（可手动调用）。 */
  handleNotification(event: NotificationEvent): { processed: boolean; reason?: string } {
    // 1. 白名单过滤
    if (!this.whitelist.includes(event.packageName)) {
      logger.debug('notification', `[通知过滤] 非白名单包: ${event.packageName}`);
      return { processed: false, reason: '非白名单包' };
    }

    const text = `${event.title} ${event.text}`;

    // 2. 关键词过滤（黑白名单）
    if (this.keywordFilter && !passesKeywordFilter(text, this.keywordFilter)) {
      logger.debug('notification', `[通知过滤] 关键词未命中: ${event.packageName}`);
      return { processed: false, reason: '关键词过滤' };
    }

    // 3. MD5 去重
    const md5 = hash(`${event.packageName}|${event.title}|${event.text}`);
    if (this.md5Hashes.has(md5)) {
      logger.debug('notification', `[通知去重] MD5 重复: ${event.packageName}`);
      return { processed: false, reason: 'MD5 去重' };
    }
    this.md5Hashes.add(md5);
    // 批量淘汰：超出上限时裁剪到 80%，避免高频通知场景下持续膨胀
    if (this.md5Hashes.size > 1000) {
      const target = 800;
      const iter = this.md5Hashes.values();
      while (this.md5Hashes.size > target) {
        const oldest = iter.next().value;
        if (oldest === undefined) break;
        this.md5Hashes.delete(oldest);
      }
    }

    // 4. 解析
    const bill = parseNotification(event);
    if (bill && this.handler) {
      this.handler(bill, event);
    } else if (!bill) {
      logger.debug('notification', `[通知解析] 未命中账单规则: ${event.packageName}`);
    }
    return { processed: !!bill, reason: bill ? undefined : '解析未命中' };
  }

  /** 更新白名单。 */
  setWhitelist(packages: string[]): void {
    this.whitelist = packages;
  }

  /** 重置去重。 */
  reset(): void {
    this.md5Hashes.clear();
  }
}

/** 模拟通知监听器（测试用）。 */
export class SimulatedNotificationListener implements NotificationListener {
  private listening = false;
  private handler: ((e: NotificationEvent) => void) | null = null;

  async start(handler: (e: NotificationEvent) => void): Promise<void> {
    this.handler = handler;
    this.listening = true;
  }
  async stop(): Promise<void> {
    this.listening = false;
    this.handler = null;
  }
  async requestRebind(): Promise<void> {}
  async setWhitelist(): Promise<void> {}
  isListening(): boolean { return this.listening; }
  /** 测试辅助。 */
  simulate(event: NotificationEvent): void {
    this.handler?.(event);
  }
}
