/**
 * Git 仓库管理类
 *
 * 封装 simple-git，提供 Git 操作的基础功能
 * 参考 aider 的 repo.py 实现
 */

import { simpleGit, type SimpleGit, type StatusResult } from 'simple-git';
import * as path from 'path';
import type {
  GitStatus,
  FileChange,
  CommitInfo,
  DiffResult,
  FileDiff,
  DiffHunk,
  ChangeStatus,
  BranchInfo,
  GitConfig,
  CommitOptions,
  CommitResult,
  AttributionConfig,
} from './types.js';

export class GitRepo {
  private git: SimpleGit;
  private workdir: string;
  private config: GitConfig;

  /** 当前会话中 AI 生成的提交哈希 */
  private aiCommitHashes: Set<string> = new Set();

  constructor(workdir: string, config: GitConfig) {
    this.workdir = workdir;
    this.config = config;
    this.git = simpleGit(workdir);
  }

  /**
   * 初始化并验证是否为 Git 仓库
   */
  async initialize(): Promise<boolean> {
    try {
      const isRepo = await this.git.checkIsRepo();
      return isRepo;
    } catch {
      return false;
    }
  }

  /**
   * 获取仓库根目录
   */
  async getRoot(): Promise<string> {
    const root = await this.git.revparse(['--show-toplevel']);
    return root.trim();
  }

  /**
   * 获取仓库状态
   */
  async getStatus(): Promise<GitStatus> {
    const status = await this.git.status();

    return {
      branch: status.current || 'HEAD',
      ahead: status.ahead,
      behind: status.behind,
      staged: this.parseStatusFiles(status.staged, status),
      unstaged: this.parseStatusFiles(status.modified, status),
      untracked: status.not_added,
      hasConflicts: status.conflicted.length > 0,
      conflicts: status.conflicted,
      isDirty: status.files.length > 0,
    };
  }

  /**
   * 检查文件是否为脏状态
   */
  async isDirty(filePath?: string): Promise<boolean> {
    const status = await this.git.status();

    if (!filePath) {
      return status.files.length > 0;
    }

    const normalizedPath = this.normalizePath(filePath);
    return status.files.some(
      (f) => this.normalizePath(f.path) === normalizedPath
    );
  }

  /**
   * 获取差异
   */
  async getDiff(options: {
    staged?: boolean;
    file?: string;
    commit?: string;
  } = {}): Promise<DiffResult> {
    let diffOutput: string;

    try {
      if (options.commit) {
        diffOutput = await this.git.diff([`${options.commit}^`, options.commit]);
      } else if (options.staged) {
        const args = ['--cached'];
        if (options.file) args.push('--', options.file);
        diffOutput = await this.git.diff(args);
      } else {
        const args: string[] = [];
        if (options.file) args.push('--', options.file);
        diffOutput = await this.git.diff(args);
      }
    } catch {
      diffOutput = '';
    }

    return this.parseDiff(diffOutput);
  }

  /**
   * 获取两个提交之间的差异
   */
  async getDiffBetween(fromCommit: string, toCommit: string): Promise<DiffResult> {
    const diffOutput = await this.git.diff([fromCommit, toCommit]);
    return this.parseDiff(diffOutput);
  }

  /**
   * 暂存文件
   */
  async stage(files: string | string[]): Promise<void> {
    const fileList = Array.isArray(files) ? files : [files];
    await this.git.add(fileList);
  }

  /**
   * 取消暂存文件
   */
  async unstage(files: string | string[]): Promise<void> {
    const fileList = Array.isArray(files) ? files : [files];
    await this.git.reset(['HEAD', '--', ...fileList]);
  }

  /**
   * 提交变更
   */
  async commit(options: CommitOptions = {}): Promise<CommitResult> {
    try {
      // 暂存文件
      if (options.files && options.files.length > 0) {
        await this.stage(options.files);
      } else if (options.all) {
        await this.git.add('.');
      }

      // 检查是否有可提交的内容
      const status = await this.git.status();
      if (status.staged.length === 0) {
        return {
          success: false,
          error: 'Nothing to commit',
        };
      }

      // 构建提交消息
      let message = options.message || 'Update files';

      // 添加属性标记
      if (options.aiEdits && this.config.attribution.coAuthoredBy) {
        message += `\n\nCo-authored-by: ${this.config.attribution.markerName} <noreply@ai-assistant.local>`;
      }

      // 设置作者信息 (如果需要标记)
      const commitOptions: string[] = [];
      if (options.aiEdits && this.config.attribution.attributeAuthor) {
        const originalName = await this.getConfigValue('user.name');
        if (originalName) {
          commitOptions.push(
            '--author',
            `${originalName} (${this.config.attribution.markerName}) <${await this.getConfigValue('user.email')}>`
          );
        }
      }

      // 执行提交
      const result = await this.git.commit(message, undefined, {
        '--no-verify': null, // 跳过 hooks 以避免干扰
        ...Object.fromEntries(commitOptions.map((opt, i) =>
          i % 2 === 0 ? [opt, commitOptions[i + 1]] : []
        ).filter(arr => arr.length > 0)),
      });

      const hash = result.commit;
      const shortHash = hash.slice(0, 7);

      // 记录 AI 提交
      if (options.aiEdits) {
        this.aiCommitHashes.add(shortHash);
      }

      return {
        success: true,
        hash,
        shortHash,
        message: message.split('\n')[0], // 只返回第一行
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }

  /**
   * 获取提交信息
   */
  async getCommitInfo(hash: string): Promise<CommitInfo | null> {
    try {
      const log = await this.git.log({
        maxCount: 1,
        from: hash,
        to: hash,
      });

      if (log.all.length === 0) {
        return null;
      }

      const entry = log.all[0];
      const files = await this.getCommitFiles(hash);

      return {
        hash: entry.hash,
        shortHash: entry.hash.slice(0, 7),
        message: entry.message,
        author: entry.author_name,
        email: entry.author_email,
        date: new Date(entry.date),
        files,
        isAIGenerated: this.aiCommitHashes.has(entry.hash.slice(0, 7)),
      };
    } catch {
      return null;
    }
  }

  /**
   * 获取最近的提交
   */
  async getRecentCommits(count: number = 10): Promise<CommitInfo[]> {
    const log = await this.git.log({ maxCount: count });
    const commits: CommitInfo[] = [];

    for (const entry of log.all) {
      const files = await this.getCommitFiles(entry.hash);
      commits.push({
        hash: entry.hash,
        shortHash: entry.hash.slice(0, 7),
        message: entry.message,
        author: entry.author_name,
        email: entry.author_email,
        date: new Date(entry.date),
        files,
        isAIGenerated: this.aiCommitHashes.has(entry.hash.slice(0, 7)),
      });
    }

    return commits;
  }

  /**
   * 获取 HEAD 提交哈希
   */
  async getHeadCommit(): Promise<string | null> {
    try {
      const hash = await this.git.revparse(['HEAD']);
      return hash.trim();
    } catch {
      return null;
    }
  }

  /**
   * 获取 HEAD 短哈希
   */
  async getHeadShortHash(): Promise<string | null> {
    try {
      const hash = await this.git.revparse(['--short', 'HEAD']);
      return hash.trim();
    } catch {
      return null;
    }
  }

  /**
   * 检查提交是否已推送到远程
   */
  async isCommitPushed(hash: string): Promise<boolean> {
    try {
      const branch = await this.git.revparse(['--abbrev-ref', 'HEAD']);
      const remoteBranch = `origin/${branch.trim()}`;

      // 检查远程分支是否存在
      try {
        await this.git.revparse([remoteBranch]);
      } catch {
        return false; // 远程分支不存在
      }

      // 检查提交是否在远程分支上
      const result = await this.git.raw([
        'branch', '-r', '--contains', hash
      ]);

      return result.includes(remoteBranch);
    } catch {
      return false;
    }
  }

  /**
   * 软重置到指定提交
   */
  async resetSoft(commit: string): Promise<void> {
    await this.git.reset(['--soft', commit]);
  }

  /**
   * 检出文件
   */
  async checkoutFile(commit: string, filePath: string): Promise<void> {
    await this.git.checkout([commit, '--', filePath]);
  }

  /**
   * 获取分支列表
   */
  async getBranches(): Promise<BranchInfo[]> {
    const summary = await this.git.branch(['-vv']);
    const branches: BranchInfo[] = [];

    for (const [name, data] of Object.entries(summary.branches)) {
      branches.push({
        name,
        current: data.current,
        remote: undefined, // simple-git 不直接提供
        upstream: undefined,
        ahead: 0,
        behind: 0,
      });
    }

    return branches;
  }

  /**
   * 获取当前分支名
   */
  async getCurrentBranch(): Promise<string> {
    const branch = await this.git.revparse(['--abbrev-ref', 'HEAD']);
    return branch.trim();
  }

  /**
   * 获取提交涉及的文件
   */
  private async getCommitFiles(hash: string): Promise<string[]> {
    try {
      const result = await this.git.raw([
        'diff-tree', '--no-commit-id', '--name-only', '-r', hash
      ]);
      return result.trim().split('\n').filter(Boolean);
    } catch {
      return [];
    }
  }

  /**
   * 获取 Git 配置值
   */
  private async getConfigValue(key: string): Promise<string | null> {
    try {
      const value = await this.git.getConfig(key);
      return value.value || null;
    } catch {
      return null;
    }
  }

  /**
   * 解析状态文件
   */
  private parseStatusFiles(files: string[], status: StatusResult): FileChange[] {
    return files.map((file) => {
      const fileStatus = status.files.find((f) => f.path === file);
      return {
        path: file,
        status: this.mapStatus(fileStatus?.index || 'M'),
        additions: 0,
        deletions: 0,
      };
    });
  }

  /**
   * 映射状态字符到 ChangeStatus
   */
  private mapStatus(code: string): ChangeStatus {
    switch (code) {
      case 'A':
      case '?':
        return 'added';
      case 'D':
        return 'deleted';
      case 'R':
        return 'renamed';
      case 'C':
        return 'copied';
      case 'M':
      default:
        return 'modified';
    }
  }

  /**
   * 解析 diff 输出
   */
  private parseDiff(diffOutput: string): DiffResult {
    const files: FileDiff[] = [];
    let totalInsertions = 0;
    let totalDeletions = 0;

    if (!diffOutput.trim()) {
      return { files, stats: { filesChanged: 0, insertions: 0, deletions: 0 } };
    }

    const fileSections = diffOutput.split(/(?=diff --git)/);

    for (const section of fileSections) {
      if (!section.trim()) continue;

      const pathMatch = section.match(/diff --git a\/(.*) b\/(.*)/);
      if (!pathMatch) continue;

      const hunks: DiffHunk[] = [];
      const hunkMatches = section.matchAll(
        /@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@([\s\S]*?)(?=@@|$)/g
      );

      for (const match of hunkMatches) {
        const content = match[5] || '';
        const adds = (content.match(/^\+[^+]/gm) || []).length;
        const dels = (content.match(/^-[^-]/gm) || []).length;
        totalInsertions += adds;
        totalDeletions += dels;

        hunks.push({
          oldStart: parseInt(match[1]),
          oldLines: parseInt(match[2]) || 1,
          newStart: parseInt(match[3]),
          newLines: parseInt(match[4]) || 1,
          content,
        });
      }

      files.push({
        path: pathMatch[2],
        oldPath: pathMatch[1] !== pathMatch[2] ? pathMatch[1] : undefined,
        status: this.detectDiffStatus(section),
        hunks,
        binary: section.includes('Binary files'),
      });
    }

    return {
      files,
      stats: {
        filesChanged: files.length,
        insertions: totalInsertions,
        deletions: totalDeletions,
      },
    };
  }

  /**
   * 检测 diff 中的变更状态
   */
  private detectDiffStatus(diffSection: string): ChangeStatus {
    if (diffSection.includes('new file mode')) return 'added';
    if (diffSection.includes('deleted file mode')) return 'deleted';
    if (diffSection.includes('rename from')) return 'renamed';
    if (diffSection.includes('copy from')) return 'copied';
    return 'modified';
  }

  /**
   * 规范化文件路径
   */
  private normalizePath(filePath: string): string {
    return path.normalize(filePath).replace(/\\/g, '/');
  }

  /**
   * 获取 AI 提交哈希集合
   */
  getAICommitHashes(): Set<string> {
    return this.aiCommitHashes;
  }

  /**
   * 检查提交是否为 AI 生成
   */
  isAICommit(shortHash: string): boolean {
    return this.aiCommitHashes.has(shortHash);
  }

  /**
   * 添加 AI 提交哈希
   */
  addAICommitHash(shortHash: string): void {
    this.aiCommitHashes.add(shortHash);
  }

  /**
   * 移除 AI 提交哈希
   */
  removeAICommitHash(shortHash: string): void {
    this.aiCommitHashes.delete(shortHash);
  }
}
