import { commitMobileTransaction } from './mobile';
import { hash, serializeTransaction } from '../core/ledger';
import type { LedgerIndex, TransactionDraft } from '../core/types';
import type { MobileCommit } from './mobile';
import { logger } from '../../utils/logger';

/**
 * main.bean 的工程化存储：并发写入锁、原子写入、崩溃恢复。
 *
 * 设计要点：
 * - 并发写入串行化（Promise 队列），避免多通道并发导致内容交错
 * - 原子写入（临时文件 + rename），中途崩溃不损坏原文件
 * - 崩溃恢复（启动时检测 tmp 残留），丢弃未完成写入
 *
 * 持久化通过注入的 MobileBeanBackend 抽象，使本模块可单测（无需 expo-file-system）。
 */

/** 持久化后端抽象（生产环境用 expo-file-system，测试用内存实现）。 */
export interface MobileBeanBackend {
  /** 读取当前 main.bean 内容；不存在返回空串。 */
  read(): Promise<string>;
  /** 原子写入：写 tmp → rename，保证中途崩溃不损坏原文件。 */
  writeAtomic(content: string): Promise<void>;
  /** 检测并清理上次崩溃的残留（启动时调用）。返回是否有残留。 */
  recoverIfNeeded(): Promise<boolean>;
}

/**
 * 内存后端（测试与 demo 用）。不真正做原子性，但记录写入次数便于断言。
 */
export class MemoryBackend implements MobileBeanBackend {
  private content = '';
  private tmp: string | null = null;
  public writeCount = 0;
  public recovered = false;

  constructor(initial = '') {
    this.content = initial;
  }

  async read(): Promise<string> {
    return this.content;
  }

  async writeAtomic(content: string): Promise<void> {
    this.tmp = content;            // 模拟写 tmp
    this.content = this.tmp;        // 模拟 rename
    this.tmp = null;
    this.writeCount++;
  }

  async recoverIfNeeded(): Promise<boolean> {
    // 内存后端无持久化，无残留；但若注入了 tmp（模拟崩溃），则清理
    if (this.tmp !== null) {
      this.tmp = null;
      this.recovered = true;
      return true;
    }
    return false;
  }

  /** 测试辅助：模拟「写了 tmp 但未 rename」的崩溃场景。 */
  simulateCrashMidWrite(content: string): void {
    this.tmp = content;
  }
}

/**
 * main.bean 存储：所有写入入口串行化，保证并发安全。
 *
 * 用法：
 *   const store = new MobileBeanStore(backend);
 *   await store.init();              // 启动时恢复
 *   const commit = await store.append(draft, ledger);
 */
export class MobileBeanStore {
  private current = '';
  /** 串行化锁：所有写操作排队执行。 */
  private chain: Promise<unknown> = Promise.resolve();
  private initialized = false;
  private _recovered = false;
  private _initPromise: Promise<void> | null = null;

  constructor(private readonly backend: MobileBeanBackend) {}

  /** 启动时调用：恢复崩溃残留 + 加载当前内容。并发调用共享同一 Promise。 */
  async init(): Promise<void> {
    if (this.initialized) return;
    if (this._initPromise) return this._initPromise;
    this._initPromise = (async () => {
      this._recovered = await this.backend.recoverIfNeeded();
      if (this._recovered) {
        logger.warn('mobileStore', '启动检查：从写入中崩溃残余 (.tmp) 中恢复并清理了临时文件');
      }
      this.current = await this.backend.read();
      this.initialized = true;
    })().catch(err => {
      logger.error('mobileStore', '初始化存储失败', err);
      // 失败后清除 promise，允许重试
      this._initPromise = null;
      throw err;
    });
    return this._initPromise;
  }

  /** 上次 init 是否检测到崩溃残留并恢复。 */
  get recovered(): boolean {
    return this._recovered;
  }

  /** 当前内存中的 main.bean 内容（init 后可用）。 */
  getContent(): string {
    return this.current;
  }

  /** 从 backend 重新读取内容以更新内存缓存（用于外部修改感知）。 */
  async reload(): Promise<void> {
    this.current = await this.backend.read();
  }

  /**
   * 追加一笔交易到 main.bean。
   * 串行化：多通道并发调用会排队，保证内容不交错。
   */
  append(draft: TransactionDraft, ledger: LedgerIndex): Promise<MobileCommit> {
    return this.enqueue(async () => {
      const commit = commitMobileTransaction(draft, ledger, this.current);
      const prev = this.current;
      this.current = commit.content;
      try {
        await this.backend.writeAtomic(this.current);
      } catch (e) {
        // 写入失败时回滚内存状态，防止内存与磁盘不一致
        logger.error('mobileStore', `追加交易失败，内存已回滚. 摘要: "${draft.narration}", 金额: ${draft.postings[0]?.amount}`, e);
        this.current = prev;
        throw e;
      }
      return commit;
    });
  }

  /** 用已解析的事件 id 标记来源（便于去重查询关联到已入账交易）。 */
  appendWithSource(draft: TransactionDraft, ledger: LedgerIndex, sourceEventIds?: string[]): Promise<MobileCommit> {
    if (sourceEventIds) draft = { ...draft, sourceEventIds };
    return this.append(draft, ledger);
  }

  /**
   * 直接替换内容（恢复/导入场景，绕过单笔校验）。
   * 仍走串行化锁与原子写入。
   */
  replace(content: string): Promise<void> {
    return this.enqueue(async () => {
      const prev = this.current;
      this.current = content;
      try {
        await this.backend.writeAtomic(this.current);
      } catch (e) {
        logger.error('mobileStore', '替换 main.bean 内容失败，内存已回滚', e);
        this.current = prev;
        throw e;
      }
    });
  }

  /**
   * 原子修改内容：在锁内执行 read-modify-write，避免竞态。
   * 用于 autoOpenAccounts/adjustAccountBalance 等需要先读后写的场景。
   */
  modifyContent(fn: (current: string) => string): Promise<void> {
    return this.enqueue(async () => {
      const prev = this.current;
      this.current = fn(this.current);
      try {
        await this.backend.writeAtomic(this.current);
      } catch (e) {
        logger.error('mobileStore', '原子修改 main.bean 失败，内存已回滚', e);
        this.current = prev;
        throw e;
      }
    });
  }

  /**
   * 当前内容的 checksum（FNV-1a），用于缓存失效判断（替代不可靠的 content.length）。
   * 反映 init() 后加载的内容；未 init 时为空串的 hash。
   */
  checksum(): string {
    return hash(this.current);
  }

  /** 序列化单笔 draft（不写入，仅预览）。 */
  preview(draft: TransactionDraft): string {
    return serializeTransaction(draft);
  }

  /** 串行化执行：保证同一 store 上的写操作互斥。 */
  private enqueue<T>(task: () => Promise<T>): Promise<T> {
    const run = this.chain.then(task, task);
    // 不让单次失败阻塞后续排队（错误冒泡给调用方，但链继续）
    this.chain = run.then(() => undefined, () => undefined);
    return run;
  }
}

/**
 * 从导入事件提取已入账事件 id（用于去重标记）。
 * 移动端写入后，sourceEventIds 即对应 imported_events.id 列表。
 */
export function extractSourceEventIds(draft: TransactionDraft): string[] | undefined {
  return draft.sourceEventIds;
}

/** 便捷类型：存储 + 后端 的工厂（生产环境注入 FileSystemBackend）。 */
export interface MobileStoreFactory {
  backend: MobileBeanBackend;
  store: MobileBeanStore;
}

export function createMobileStore(backend: MobileBeanBackend): MobileBeanStore {
  return new MobileBeanStore(backend);
}
