/**
 * 规则引擎增强（plan.md「2.6 规则引擎增强 JS 规则执行器」）。
 *
 * 参考 AutoAccounting 的 RuleGenerator（QuickJS，系统/用户规则分离）。
 *
 * 本模块定义规则执行器抽象：
 * - JsRuleExecutor：在隔离上下文执行用户自定义 JS 规则
 * - 生产环境可用 QuickJS（原生模块），测试环境用 Function 构造器的轻量沙箱
 *
 * 规则优先级：用户规则 > 系统规则；禁用规则可阻止 AI 触发（AutoAccounting RuleScope）。
 */

import type { ImportedEvent } from '../core/types';
import { matchesStaticConditions } from './rules';

export interface EnhancedRule {
  id: string;
  name: string;
  priority: number;
  enabled: boolean;
  isSystem: boolean;        // 系统规则 vs 用户规则
  // 静态匹配条件（快速预筛选）
  direction?: ImportedEvent['direction'];
  counterpartyContains?: string;
  memoContains?: string;
  counterpartyNotContains?: string;
  memoNotContains?: string;
  minAmount?: string;
  maxAmount?: string;
  currency?: string;
  // JS 规则体（动态精确匹配 + 字段提取）
  jsCode?: string;
  // 输出
  sourceAccount: string;
  categoryAccount: string;
  narration?: string;
  tags?: string[];
  // 统计
  hits: number;
  lastHitAt?: string;
}

export interface RuleExecutionResult {
  matched: boolean;
  extracted?: Record<string, unknown>;
}

/** 规则执行器抽象（生产用 QuickJS，测试用 Function 沙箱）。 */
export interface JsRuleExecutor {
  /** 在隔离上下文执行 JS 规则，注入 data 对象。 */
  execute(jsCode: string, data: Record<string, unknown>): RuleExecutionResult;
}

/**
 * 轻量 JS 沙箱（用 Function 构造器，无原生依赖）。
 * 生产环境应替换为 QuickJS（更严格的隔离），但本实现满足单测需求。
 *
 * 安全措施：冻结全局关键属性，防止规则代码修改或逃逸。
 */
export class FunctionSandboxExecutor implements JsRuleExecutor {
  execute(jsCode: string, data: Record<string, unknown>): RuleExecutionResult {
    try {
      // 冻结 data 防止规则代码修改传入对象
      const frozenData = Object.freeze({ ...data });
      // 冻结的空全局对象，阻止访问 window/globalThis/require 等
      const frozenGlobal = Object.freeze({});
      // TODO: 生产环境应替换为 QuickJS 隔离执行
      const fn = new Function('data', 'print', 'global', `with(global) {\n${jsCode}\n}`);
      const output: string[] = [];
      const print = (s: string) => { output.push(s); };
      const start = Date.now();
      fn(frozenData, print, frozenGlobal);
      if (Date.now() - start > 50) return { matched: false };
      // 规则通过 print(JSON.stringify(result)) 输出
      const json = output.find(o => o.trim().startsWith('{'));
      if (!json) return { matched: false };
      const extracted = JSON.parse(json) as Record<string, unknown>;
      // matched 判定：提取到 money 且 > 0
      const money = extracted.money ?? extracted.amount;
      const matched = money !== undefined && parseFloat(String(money)) > 0;
      return { matched, extracted };
    } catch {
      return { matched: false };
    }
  }
}

/**
 * 规则引擎：批量执行规则，返回首个命中。
 * 匹配顺序：用户规则优先（isSystem=false 先），同类型按 priority 降序。
 */
export class RuleEngine {
  constructor(private readonly executor: JsRuleExecutor = new FunctionSandboxExecutor()) {}

  matchEvent(event: ImportedEvent, rules: EnhancedRule[]): { rule: EnhancedRule; result: RuleExecutionResult } | null {
    // 排序：用户规则 > 系统规则；同类型按 priority 降序
    const sorted = [...rules]
      .filter(r => r.enabled)
      .sort((a, b) => {
        if (a.isSystem !== b.isSystem) return a.isSystem ? 1 : -1;
        return b.priority - a.priority;
      });

    for (const rule of sorted) {
      // 静态条件预筛选
      if (!this.matchStaticConditions(rule, event)) continue;

      // JS 规则执行（若有）
      if (rule.jsCode) {
        const result = this.executor.execute(rule.jsCode, this.eventToData(event));
        if (result.matched) {
          return { rule: { ...rule, hits: rule.hits + 1, lastHitAt: new Date().toISOString() }, result };
        }
      } else {
        // 无 JS 代码，静态匹配成功即命中
        return { rule: { ...rule, hits: rule.hits + 1, lastHitAt: new Date().toISOString() }, result: { matched: true } };
      }
    }
    return null;
  }

  /** 静态条件预筛选（复用 rules.ts 的公共实现）。 */
  private matchStaticConditions(rule: EnhancedRule, event: ImportedEvent): boolean {
    return matchesStaticConditions(event, rule);
  }

  /** 把 ImportedEvent 转为规则执行数据。 */
  private eventToData(event: ImportedEvent): Record<string, unknown> {
    return {
      app: event.raw?.packageName || '',
      type: event.direction,
      money: event.amount,
      shopName: event.counterparty,
      shopItem: event.memo,
      time: event.occurredAt,
      currency: event.currency,
      ...event.raw,
    };
  }
}
