/**
 * 孤儿文件清理与维护（plan.md「Phase 10」+「10.1 孤儿文件扫描」+「10.2 数据库维护」）。
 *
 * 参考 BeeCount 的 Orphan File GC：
 * - 扫描孤立附件（无交易引用的附件文件）
 * - 扫描孤立缩略图
 * - 数据库压缩（VACUUM）
 * - 数据完整性检查
 *
 * 文件系统与数据库通过注入抽象，使可单测。
 */

import { logger } from '../../utils/logger';

/** 文件系统抽象（扫描/删除/大小查询）。 */
export interface MaintenanceFs {
  listFiles(dir: string): Promise<string[]>;
  deleteFile(path: string): Promise<void>;
  getFileSize(path: string): Promise<number>;
}

/** 数据库抽象（VACUUM / 完整性检查）。 */
export interface MaintenanceDb {
  vacuum(): Promise<void>;
  /** 数据完整性检查，返回问题列表。 */
  integrityCheck(): Promise<string[]>;
}

export interface OrphanFile {
  type: 'attachment' | 'thumbnail' | 'cache';
  path: string;
  size: number;
}

export interface MaintenanceReport {
  orphanFiles: OrphanFile[];
  totalOrphanSize: number;
  dbIssues: string[];
  cleanedCount: number;
}

/**
 * 扫描孤儿文件。
 * @param usedUris 被交易/附件引用的 URI 集合
 * @param scanDirs 要扫描的目录（附件目录、缩略图目录、缓存目录）
 */
export async function scanOrphanFiles(
  fs: MaintenanceFs,
  usedUris: Set<string>,
  scanDirs: { attachments: string; thumbnails: string; cache: string },
): Promise<OrphanFile[]> {
  const orphans: OrphanFile[] = [];

  // 扫描孤立附件
  try {
    const attachments = await fs.listFiles(scanDirs.attachments);
    for (const path of attachments) {
      if (!usedUris.has(path)) {
        const size = await fs.getFileSize(path).catch(() => 0);
        orphans.push({ type: 'attachment', path, size });
      }
    }
  } catch (e) {
    logger.warn('maintenance', `扫描附件目录失败: ${e instanceof Error ? e.message : e}`);
  }

  // 扫描孤立缩略图
  try {
    const thumbnails = await fs.listFiles(scanDirs.thumbnails);
    for (const path of thumbnails) {
      const size = await fs.getFileSize(path).catch(() => 0);
      orphans.push({ type: 'thumbnail', path, size });
    }
  } catch (e) {
    logger.warn('maintenance', `扫描缩略图目录失败: ${e instanceof Error ? e.message : e}`);
  }

  // 扫描缓存
  try {
    const cacheFiles = await fs.listFiles(scanDirs.cache);
    for (const path of cacheFiles) {
      const size = await fs.getFileSize(path).catch(() => 0);
      orphans.push({ type: 'cache', path, size });
    }
  } catch (e) {
    logger.warn('maintenance', `扫描缓存目录失败: ${e instanceof Error ? e.message : e}`);
  }

  return orphans;
}

/**
 * 清理孤儿文件。
 * @returns 实际清理的文件数
 */
export async function cleanOrphanFiles(fs: MaintenanceFs, orphans: OrphanFile[]): Promise<number> {
  let cleaned = 0;
  for (const file of orphans) {
    try {
      await fs.deleteFile(file.path);
      cleaned++;
      logger.debug('maintenance', `已清理孤立文件: ${file.path}`);
    } catch (e) {
      logger.warn('maintenance', `清理失败: ${file.path} - ${e instanceof Error ? e.message : e}`);
    }
  }
  return cleaned;
}

/**
 * 执行完整维护：扫描 + 清理 + 数据库压缩 + 完整性检查。
 */
export async function runMaintenance(
  fs: MaintenanceFs,
  db: MaintenanceDb,
  usedUris: Set<string>,
  scanDirs: { attachments: string; thumbnails: string; cache: string },
): Promise<MaintenanceReport> {
  // 1. 扫描孤儿文件
  const orphans = await scanOrphanFiles(fs, usedUris, scanDirs);
  const totalOrphanSize = orphans.reduce((sum, f) => sum + f.size, 0);

  // 2. 清理
  const cleanedCount = await cleanOrphanFiles(fs, orphans);

  // 3. 数据库压缩
  try {
    await db.vacuum();
    logger.info('maintenance', '数据库 VACUUM 完成');
  } catch (e) {
    logger.warn('maintenance', `数据库 VACUUM 失败: ${e instanceof Error ? e.message : e}`);
  }

  // 4. 完整性检查
  const dbIssues = await db.integrityCheck().catch(() => []);

  logger.info('maintenance', `维护完成: 清理 ${cleanedCount} 个文件，${dbIssues.length} 个数据库问题`);
  return { orphanFiles: orphans, totalOrphanSize, dbIssues, cleanedCount };
}

/** 模拟文件系统（测试用）。 */
export class MockMaintenanceFs implements MaintenanceFs {
  public files = new Map<string, number>();  // path → size
  public deleted: string[] = [];

  setFile(path: string, size: number): void {
    this.files.set(path, size);
  }
  async listFiles(dir: string): Promise<string[]> {
    return [...this.files.keys()].filter(p => p.startsWith(dir));
  }
  async deleteFile(path: string): Promise<void> {
    this.files.delete(path);
    this.deleted.push(path);
  }
  async getFileSize(path: string): Promise<number> {
    return this.files.get(path) ?? 0;
  }
}

/** 模拟数据库（测试用）。 */
export class MockMaintenanceDb implements MaintenanceDb {
  public vacuumed = false;
  public issues: string[] = [];
  async vacuum(): Promise<void> { this.vacuumed = true; }
  async integrityCheck(): Promise<string[]> { return this.issues; }
}

/**
 * 生产文件系统后端：基于 expo-file-system 扫描和清理。
 *
 * 扫描 documentDirectory + cacheDirectory 下的孤儿文件（附件/缩略图/缓存）。
 */
export class ExpoMaintenanceFs implements MaintenanceFs {
  async listFiles(dir: string): Promise<string[]> {
    try {
      // 动态导入避免测试环境引入 RN 依赖
      const FileSystem = require('expo-file-system/legacy');
      const info = await FileSystem.getInfoAsync(dir);
      if (!info.exists || !info.isDirectory) return [];
      const entries = await FileSystem.readDirectoryAsync(dir);
      return entries.map((name: string) => `${dir}${dir.endsWith('/') ? '' : '/'}${name}`);
    } catch {
      return [];
    }
  }

  async deleteFile(path: string): Promise<void> {
    const FileSystem = require('expo-file-system/legacy');
    await FileSystem.deleteAsync(path, { idempotent: true });
  }

  async getFileSize(path: string): Promise<number> {
    try {
      const FileSystem = require('expo-file-system/legacy');
      const info = await FileSystem.getInfoAsync(path);
      return info.exists ? (info.size ?? 0) : 0;
    } catch {
      return 0;
    }
  }
}

/**
 * 生产数据库后端：本应用使用 .bean 文本文件，无 SQL 数据库，
 * 因此 VACUUM/integrityCheck 为空操作（只校验账本文件可读性）。
 */
export class ExpoMaintenanceDb implements MaintenanceDb {
  constructor(private readonly mainBeanContent: string = '') {}

  async vacuum(): Promise<void> {
    // .bean 是文本文件，无需 VACUUM。保留接口兼容。
  }

  async integrityCheck(): Promise<string[]> {
    const issues: string[] = [];
    if (!this.mainBeanContent.trim()) {
      issues.push('main.bean 为空');
    }
    return issues;
  }
}

