/**
 * Shadow Git 存储实现
 * 使用隔离的 Git 仓库存储检查点，不影响用户的主仓库
 */

import * as fs from 'fs/promises';
import * as path from 'path';
import { execFile } from 'child_process';
import { promisify } from 'util';
import type { FileChange, DiffInfo, FileDiff } from './types.js';

const execFileAsync = promisify(execFile);

/**
 * 计算工作目录哈希
 * 使用 31 进制哈希算法，与 Cline 保持一致
 */
export function hashWorkingDir(workingDir: string): string {
  let hash = 0;
  for (let i = 0; i < workingDir.length; i++) {
    hash = ((hash * 31 + workingDir.charCodeAt(i)) >>> 0) % 2147483647;
  }
  return hash.toString().slice(0, 13).padStart(13, '0');
}

/**
 * 需要排除的目录列表
 */
const EXCLUDED_DIRS = [
  'node_modules',
  '.git',
  'dist',
  'build',
  '.next',
  '__pycache__',
  '.pytest_cache',
  'coverage',
  '.nyc_output',
  '.ai-assist',
];

/**
 * Shadow Git 管理器
 */
export class ShadowGit {
  private workDir: string;
  private shadowGitDir: string;
  private initialized = false;
  private cwdHash: string;

  constructor(workDir: string, storageBaseDir: string) {
    this.workDir = path.resolve(workDir);
    this.cwdHash = hashWorkingDir(this.workDir);
    this.shadowGitDir = path.join(storageBaseDir, this.cwdHash);
  }

  /**
   * 获取工作目录哈希
   */
  getCwdHash(): string {
    return this.cwdHash;
  }

  /**
   * 获取 Shadow Git 目录
   */
  getShadowGitDir(): string {
    return this.shadowGitDir;
  }

  /**
   * 初始化 Shadow Git 仓库
   */
  async initialize(): Promise<void> {
    if (this.initialized) return;

    const gitDir = path.join(this.shadowGitDir, '.git');

    try {
      await fs.access(gitDir);
      // 已存在，验证配置
      await this.verifyConfig();
    } catch {
      // 不存在，创建新仓库
      await this.createRepository();
    }

    this.initialized = true;
  }

  /**
   * 创建新的 Shadow Git 仓库
   */
  private async createRepository(): Promise<void> {
    // 创建目录
    await fs.mkdir(this.shadowGitDir, { recursive: true });

    // 初始化 Git 仓库
    await this.git(['init']);

    // 配置用户信息
    await this.git(['config', 'user.name', 'AI Assistant Checkpoint']);
    await this.git(['config', 'user.email', 'checkpoint@ai-assist.local']);

    // 配置工作目录
    await this.git(['config', 'core.worktree', this.workDir]);

    // 禁用 GPG 签名
    await this.git(['config', 'commit.gpgsign', 'false']);

    // 创建 .gitignore
    const gitignoreContent = EXCLUDED_DIRS.map((d) => `${d}/`).join('\n') + '\n';
    await fs.writeFile(
      path.join(this.shadowGitDir, '.gitignore'),
      gitignoreContent
    );

    // 创建初始提交
    await this.git(['add', '.gitignore']);
    await this.git([
      'commit',
      '--allow-empty',
      '-m',
      'Initial checkpoint repository',
    ]);
  }

  /**
   * 验证现有配置
   */
  private async verifyConfig(): Promise<void> {
    try {
      const { stdout } = await this.git(['config', 'core.worktree']);
      const configuredWorkDir = stdout.trim();

      if (configuredWorkDir !== this.workDir) {
        // 更新工作目录配置
        await this.git(['config', 'core.worktree', this.workDir]);
      }
    } catch {
      // 配置不存在，添加
      await this.git(['config', 'core.worktree', this.workDir]);
    }
  }

  /**
   * 执行 Git 命令
   */
  private async git(
    args: string[],
    options: { cwd?: string } = {}
  ): Promise<{ stdout: string; stderr: string }> {
    const cwd = options.cwd || this.shadowGitDir;
    const gitDir = path.join(this.shadowGitDir, '.git');

    try {
      const result = await execFileAsync(
        'git',
        ['--git-dir', gitDir, '--work-tree', this.workDir, ...args],
        {
          cwd,
          maxBuffer: 50 * 1024 * 1024, // 50MB
          env: {
            ...process.env,
            GIT_TERMINAL_PROMPT: '0',
          },
        }
      );
      return result;
    } catch (error: any) {
      // 某些 git 命令失败是正常的 (如空提交)
      if (error.stdout !== undefined) {
        return { stdout: error.stdout || '', stderr: error.stderr || '' };
      }
      throw error;
    }
  }

  /**
   * 创建检查点提交
   */
  async createCommit(message: string): Promise<string> {
    await this.initialize();

    // 暂时禁用嵌套 .git 目录
    const nestedGitDirs = await this.findNestedGitDirs();
    await this.renameNestedGitDirs(nestedGitDirs, true);

    try {
      // 添加所有文件
      await this.git(['add', '.', '--ignore-errors']);

      // 创建提交
      await this.git([
        'commit',
        '--allow-empty',
        '--no-verify',
        '-m',
        message,
      ]);

      // 获取 commit hash
      const { stdout } = await this.git(['rev-parse', 'HEAD']);
      return stdout.trim();
    } finally {
      // 恢复嵌套 .git 目录
      await this.renameNestedGitDirs(nestedGitDirs, false);
    }
  }

  /**
   * 查找嵌套的 .git 目录
   */
  private async findNestedGitDirs(): Promise<string[]> {
    const nestedDirs: string[] = [];

    const walk = async (dir: string, depth = 0): Promise<void> => {
      if (depth > 5) return; // 限制深度

      try {
        const entries = await fs.readdir(dir, { withFileTypes: true });

        for (const entry of entries) {
          if (!entry.isDirectory()) continue;

          const fullPath = path.join(dir, entry.name);

          // 跳过排除的目录
          if (EXCLUDED_DIRS.includes(entry.name)) continue;

          if (entry.name === '.git') {
            nestedDirs.push(fullPath);
          } else if (!entry.name.startsWith('.')) {
            await walk(fullPath, depth + 1);
          }
        }
      } catch {
        // 忽略无法访问的目录
      }
    };

    await walk(this.workDir);
    return nestedDirs;
  }

  /**
   * 重命名嵌套 .git 目录
   */
  private async renameNestedGitDirs(
    dirs: string[],
    disable: boolean
  ): Promise<void> {
    for (const dir of dirs) {
      const disabledName = dir + '_disabled';
      try {
        if (disable) {
          await fs.rename(dir, disabledName);
        } else {
          await fs.rename(disabledName, dir);
        }
      } catch {
        // 忽略错误
      }
    }
  }

  /**
   * 获取当前 HEAD commit hash
   */
  async getHead(): Promise<string> {
    await this.initialize();
    const { stdout } = await this.git(['rev-parse', 'HEAD']);
    return stdout.trim();
  }

  /**
   * 重置到指定 commit
   */
  async resetHard(commitHash: string): Promise<void> {
    await this.initialize();

    // 暂时禁用嵌套 .git 目录
    const nestedGitDirs = await this.findNestedGitDirs();
    await this.renameNestedGitDirs(nestedGitDirs, true);

    try {
      await this.git(['reset', '--hard', commitHash]);
    } finally {
      await this.renameNestedGitDirs(nestedGitDirs, false);
    }
  }

  /**
   * 获取 commit 列表
   */
  async getCommits(limit = 100): Promise<
    Array<{
      hash: string;
      message: string;
      timestamp: number;
    }>
  > {
    await this.initialize();

    const { stdout } = await this.git([
      'log',
      `--max-count=${limit}`,
      '--format=%H|%s|%ct',
    ]);

    if (!stdout.trim()) return [];

    return stdout
      .trim()
      .split('\n')
      .map((line) => {
        const [hash, message, timestamp] = line.split('|');
        return {
          hash,
          message,
          timestamp: parseInt(timestamp, 10) * 1000,
        };
      });
  }

  /**
   * 获取两个 commit 之间的差异摘要
   */
  async getDiffSummary(fromCommit: string, toCommit = 'HEAD'): Promise<DiffInfo> {
    await this.initialize();

    const { stdout } = await this.git([
      'diff',
      '--stat',
      '--numstat',
      fromCommit,
      toCommit,
    ]);

    const files: FileChange[] = [];
    let totalInsertions = 0;
    let totalDeletions = 0;

    // 解析 numstat 输出
    const lines = stdout.trim().split('\n');
    for (const line of lines) {
      const match = line.match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
      if (match) {
        const insertions = match[1] === '-' ? 0 : parseInt(match[1], 10);
        const deletions = match[2] === '-' ? 0 : parseInt(match[2], 10);
        const filePath = match[3];

        // 检测重命名
        const renameMatch = filePath.match(/^(.+)\{(.+) => (.+)\}(.*)$/);
        if (renameMatch) {
          const prefix = renameMatch[1];
          const oldName = renameMatch[2];
          const newName = renameMatch[3];
          const suffix = renameMatch[4];
          files.push({
            path: prefix + newName + suffix,
            oldPath: prefix + oldName + suffix,
            type: 'renamed',
            insertions,
            deletions,
          });
        } else {
          // 获取文件状态
          const type = await this.getFileChangeType(fromCommit, toCommit, filePath);
          files.push({
            path: filePath,
            type,
            insertions,
            deletions,
          });
        }

        totalInsertions += insertions;
        totalDeletions += deletions;
      }
    }

    return {
      from: fromCommit,
      to: toCommit,
      files,
      totalInsertions,
      totalDeletions,
    };
  }

  /**
   * 获取文件变更类型
   */
  private async getFileChangeType(
    fromCommit: string,
    toCommit: string,
    filePath: string
  ): Promise<FileChange['type']> {
    try {
      const { stdout } = await this.git([
        'diff',
        '--name-status',
        fromCommit,
        toCommit,
        '--',
        filePath,
      ]);

      const status = stdout.trim().charAt(0);
      switch (status) {
        case 'A':
          return 'added';
        case 'D':
          return 'deleted';
        case 'R':
          return 'renamed';
        default:
          return 'modified';
      }
    } catch {
      return 'modified';
    }
  }

  /**
   * 获取文件内容差异
   */
  async getFileDiff(
    fromCommit: string,
    toCommit: string,
    filePath: string
  ): Promise<FileDiff> {
    await this.initialize();

    const type = await this.getFileChangeType(fromCommit, toCommit, filePath);

    let oldContent: string | undefined;
    let newContent: string | undefined;
    let patch: string | undefined;

    try {
      if (type !== 'added') {
        const { stdout } = await this.git(['show', `${fromCommit}:${filePath}`]);
        oldContent = stdout;
      }
    } catch {
      // 文件在旧 commit 中不存在
    }

    try {
      if (type !== 'deleted') {
        const { stdout } = await this.git(['show', `${toCommit}:${filePath}`]);
        newContent = stdout;
      }
    } catch {
      // 文件在新 commit 中不存在
    }

    try {
      const { stdout } = await this.git([
        'diff',
        fromCommit,
        toCommit,
        '--',
        filePath,
      ]);
      patch = stdout;
    } catch {
      // 无法生成 diff
    }

    return {
      path: filePath,
      type,
      oldContent,
      newContent,
      patch,
    };
  }

  /**
   * 检出指定 commit 的特定文件
   */
  async checkoutFiles(commitHash: string, files: string[]): Promise<void> {
    await this.initialize();

    for (const file of files) {
      try {
        await this.git(['checkout', commitHash, '--', file]);
      } catch (error) {
        // 文件可能不存在于该 commit
        console.warn(`Failed to checkout ${file} from ${commitHash}:`, error);
      }
    }
  }

  /**
   * 获取指定 commit 中文件的内容
   */
  async getFileContent(commitHash: string, filePath: string): Promise<string | null> {
    await this.initialize();

    try {
      const { stdout } = await this.git(['show', `${commitHash}:${filePath}`]);
      return stdout;
    } catch {
      return null;
    }
  }

  /**
   * 清理旧的 commit（保留最近 N 个）
   */
  async cleanup(keepCount: number): Promise<number> {
    await this.initialize();

    const commits = await this.getCommits(keepCount + 100);

    if (commits.length <= keepCount) {
      return 0;
    }

    // 使用 git gc 清理
    try {
      await this.git(['gc', '--aggressive', '--prune=now']);
    } catch {
      // gc 可能失败，忽略
    }

    return commits.length - keepCount;
  }

  /**
   * 检查是否有未提交的变更
   */
  async hasChanges(): Promise<boolean> {
    await this.initialize();

    const { stdout } = await this.git(['status', '--porcelain']);
    return stdout.trim().length > 0;
  }

  /**
   * 获取工作目录与 HEAD 的差异
   */
  async getWorkingDirDiff(): Promise<DiffInfo> {
    await this.initialize();

    // 先添加所有文件到暂存区以检测新文件
    const nestedGitDirs = await this.findNestedGitDirs();
    await this.renameNestedGitDirs(nestedGitDirs, true);

    try {
      await this.git(['add', '.', '--ignore-errors']);
      const result = await this.getDiffSummary('HEAD', '--staged');

      // 重置暂存区
      await this.git(['reset', 'HEAD']);

      return result;
    } finally {
      await this.renameNestedGitDirs(nestedGitDirs, false);
    }
  }
}

/**
 * 创建 Shadow Git 实例
 */
export function createShadowGit(
  workDir: string,
  storageBaseDir: string
): ShadowGit {
  return new ShadowGit(workDir, storageBaseDir);
}
