/**
 * OCR 模型下载/安装服务（P8）。
 *
 * 两种安装来源，按优先级：
 * 1. 网络下载（HuggingFace / 自定义 CDN）
 * 2. 从 APK assets 拷贝（内置模型兜底）
 *
 * 下载的模型存放在 documentDirectory/ocr_models_v6，由 OcrModule.kt 的 setModelDir 加载。
 */

import * as FileSystem from 'expo-file-system/legacy';
import { useSettingsStore } from '../../store/settingsStore';
import { getNativeOcrModule } from './ocrBridge';
import { logger } from '../../utils/logger';

// ==================== 模型版本 Manifest ====================

/** 单个模型文件的描述。 */
export interface ModelFileEntry {
  /** 文件名（如 "ppocrv6_det.onnx"） */
  name: string;
  /** 下载 URL */
  url: string;
  /** SHA-256 十六进制字符串（空字符串 = 跳过校验） */
  sha256: string;
  /** 文件大小（bytes） */
  size: number;
}

/** 模型版本 manifest。 */
export interface ModelManifest {
  /** 版本字符串，如 "PP-OCRv6" */
  version: string;
  /** 模型文件列表 */
  files: ModelFileEntry[];
}

/**
 * 内置模型文件清单（PP-OCRv6 HuggingFace）。
 * SHA256 留空时跳过校验（下载后用 sha256sum 算出实际值填入即可）。
 */
const BUILTIN_MANIFEST: ModelManifest = {
  version: 'PP-OCRv6',
  files: [
    {
      name: 'ppocrv6_det.onnx',
      url: 'https://huggingface.co/PaddlePaddle/PP-OCRv6_small_det_onnx/resolve/main/inference.onnx',
      sha256: '',
      size: 9880512,
    },
    {
      name: 'ppocrv6_rec.onnx',
      url: 'https://huggingface.co/PaddlePaddle/PP-OCRv6_small_rec_onnx/resolve/main/inference.onnx',
      sha256: '',
      size: 21159378,
    },
    {
      name: 'ppocrv6_dict.txt',
      url: 'https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/dict/ppocrv6_dict.txt',
      sha256: '',
      size: 74947,
    },
  ],
};

/** 远程 manifest JSON URL（为空时使用 BUILTIN_MANIFEST）。 */
const DEFAULT_MANIFEST_URL = '';

// ==================== 工具函数 ====================

/** 模型目录路径。 */
function getModelDir(): string {
  return `${FileSystem.documentDirectory}ocr_models_v6/`;
}

/** 确保目录存在。 */
async function ensureModelDir(dir: string): Promise<void> {
  const info = await FileSystem.getInfoAsync(dir);
  if (!info.exists) {
    await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
  }
}

// ==================== 公开 API ====================

/** 首次调用标记。 */
let _ensured = false;

/**
 * 从网络下载模型文件。
 *
 * @param manifestUrl - 远端 manifest JSON URL（可选，为空时用 BUILTIN_MANIFEST）
 * @param onProgress  - 下载进度回调（0-100）
 * @returns 模型目录路径
 */
export async function downloadOcrModels(
  manifestUrl?: string,
  onProgress?: (progress: number) => void,
): Promise<string> {
  // 1. 获取 manifest
  let manifest: ModelManifest;
  const url = manifestUrl || DEFAULT_MANIFEST_URL;

  if (url) {
    onProgress?.(0);
    logger.info('modelDownloader', `正在获取模型清单: ${url}`);
    const manifestResult = await FileSystem.downloadAsync(url, `${getModelDir()}manifest.json`);
    if (manifestResult?.status !== 200) {
      throw new Error(`获取模型清单失败: HTTP ${manifestResult?.status ?? 'unknown'}`);
    }
    const manifestJson = await FileSystem.readAsStringAsync(manifestResult.uri);
    manifest = JSON.parse(manifestJson);
  } else {
    manifest = BUILTIN_MANIFEST;
  }

  logger.info('modelDownloader', `模型版本: ${manifest.version}, 共 ${manifest.files.length} 个文件`);
  await ensureModelDir(getModelDir());

  // 2. 逐文件下载，按文件粒度报进度（legacy API 的字节级回调不可靠）
  const dir = getModelDir();
  const total = manifest.files.length;

  for (let i = 0; i < manifest.files.length; i++) {
    const entry = manifest.files[i];
    const destPath = `${dir}${entry.name}`;

    // 已存在且大小匹配 → 跳过
    const existing = await FileSystem.getInfoAsync(destPath);
    if (existing.exists && (existing as unknown as { size?: number }).size === entry.size) {
      logger.info('modelDownloader', `${entry.name} 已存在，跳过下载`);
      onProgress?.(Math.round(((i + 1) / total) * 90));
      continue;
    }

    try {
      // 下载开始（文件级进度，不做字节级回调——legacy API 的 progress 回调不稳定）
      onProgress?.(Math.round((i / total) * 90 + 1));
      logger.info('modelDownloader', `下载 ${i + 1}/${total}: ${entry.name} (${(entry.size / 1024 / 1024).toFixed(1)} MB)`);

      const downloadResult = await FileSystem.downloadAsync(entry.url, destPath);
      if (downloadResult?.status !== 200) {
        throw new Error(`${entry.name}: HTTP ${downloadResult?.status ?? 'unknown'}`);
      }

      // 校验文件已实际写入（legacy API 有时返回 200 但文件为空或截断）
      const downloaded = await FileSystem.getInfoAsync(destPath);
      if (!downloaded.exists || ((downloaded as unknown as { size?: number }).size ?? 0) < 1024) {
        try { await FileSystem.deleteAsync(destPath); } catch { /* ignore */ }
        throw new Error(`${entry.name}: 下载后文件异常（大小 ${(downloaded as unknown as { size?: number }).size ?? 0} bytes），可能网络断开`);
      }

      // SHA256 校验（仅当提供时才执行）
      if (entry.sha256) {
        const valid = await verifySha256(destPath, entry.sha256);
        if (!valid) {
          try { await FileSystem.deleteAsync(destPath); } catch { /* ignore */ }
          throw new Error(`${entry.name}: SHA256 校验失败，文件可能损坏`);
        }
      }

      onProgress?.(Math.round(((i + 1) / total) * 90));
    } catch (err) {
      logger.error('modelDownloader', `${entry.name} 下载失败`, err);
      try { await FileSystem.deleteAsync(destPath); } catch { /* ignore */ }
      const sizeMB = (entry.size / 1024 / 1024).toFixed(1);
      const reason = (err as Error)?.message ? `: ${(err as Error).message}` : '';
      throw new Error(`${entry.name} (${sizeMB} MB) 下载失败${reason}`);
    }
  }

  // 3. 保存版本 + 通知原生
  onProgress?.(100);
  const store = useSettingsStore.getState();
  store.setOcrModelVersion(manifest.version);
  store.setOcrModelDir(dir);

  const nativeModule = getNativeOcrModule();
  if (nativeModule && 'setModelDir' in nativeModule) {
    try {
      await nativeModule.setModelDir(dir);
    } catch (e) {
      // 不静默吞错：记录日志便于排查。下载本身已成功，引擎加载失败会在首次 OCR 调用时暴露。
      logger.error('modelDownloader', `setModelDir 失败（下载完成后通知原生重载）`, e);
    }
  }

  logger.info('modelDownloader', `OCR 模型下载完成: ${manifest.version} → ${dir}`);
  return dir;
}

/**
 * 校验文件的 SHA-256。
 * expo-crypto 对 20MB 文件可能 OOM，异常时乐观通过。
 */
async function verifySha256(filePath: string, expected: string): Promise<boolean> {
  try {
    const Crypto = require('expo-crypto');
    const content = await FileSystem.readAsStringAsync(filePath, {
      encoding: FileSystem.EncodingType.Base64,
    });
    const actual = await Crypto.digestStringAsync(
      Crypto.CryptoDigestAlgorithm.SHA256,
      content,
      { encoding: Crypto.CryptoEncoding.HEX },
    );
    return actual.toLowerCase() === expected.toLowerCase();
  } catch (e) {
    logger.warn('modelDownloader', `[SHA256 校验跳过] 无法校验模型完整性，乐观通过`, e);
    return true; // 无法校验时乐观通过
  }
}

/**
 * 确保 OCR 模型可用：
 * 1. 已有 → 直接复用
 * 2. 网络下载（HuggingFace / 自定义 URL）
 */
export async function ensureOcrModels(
  onProgress?: (progress: number) => void,
): Promise<string> {
  const store = useSettingsStore.getState();

  // 1. 已有 → 复用
  if (_ensured && store.ocrModelDir) {
    return store.ocrModelDir;
  }
  if (store.ocrModelDir) {
    _ensured = true;
    const nativeModule = getNativeOcrModule();
    if (nativeModule && 'setModelDir' in nativeModule) {
      try { await nativeModule.setModelDir(store.ocrModelDir); } catch (e) {
        logger.error('modelDownloader', `setModelDir 失败（复用已下载模型）`, e);
      }
    }
    return store.ocrModelDir;
  }

  // 2. 网络下载
  logger.info('modelDownloader', '从网络下载 OCR 模型...');
  const manifestUrl = DEFAULT_MANIFEST_URL || undefined;
  const dir = await downloadOcrModels(manifestUrl, onProgress);
  _ensured = true;
  return dir;
}

/**
 * 仅下载模型（跳过 assets 兜底），供 UI 层「重新下载」使用。
 */
export async function redownloadModels(
  manifestUrl?: string,
  onProgress?: (progress: number) => void,
): Promise<string> {
  return downloadOcrModels(manifestUrl, onProgress);
}
