/**
 * 检查点并发控制模块
 * 参考 Cline 的 proper-lockfile 机制
 */

import * as fs from 'fs/promises';
import * as path from 'path';

/**
 * 锁文件内容
 */
interface LockInfo {
  pid: number;
  hostname: string;
  timestamp: number;
}

/**
 * 锁配置
 */
interface LockOptions {
  /** 锁过期时间（毫秒） */
  stale?: number;
  /** 重试次数 */
  retries?: number;
  /** 重试间隔（毫秒） */
  retryDelay?: number;
}

const DEFAULT_OPTIONS: Required<LockOptions> = {
  stale: 30000, // 30 秒过期
  retries: 10,
  retryDelay: 100,
};

/**
 * 检查点锁管理器
 * 防止多个实例同时操作同一工作区的检查点
 */
export class CheckpointLock {
  private lockPath: string;
  private locked = false;
  private options: Required<LockOptions>;

  constructor(shadowGitDir: string, options: LockOptions = {}) {
    this.lockPath = path.join(shadowGitDir, '.checkpoint.lock');
    this.options = { ...DEFAULT_OPTIONS, ...options };
  }

  /**
   * 获取锁
   */
  async acquire(): Promise<void> {
    let lastError: Error | null = null;

    for (let i = 0; i < this.options.retries; i++) {
      try {
        await this.tryAcquire();
        this.locked = true;
        return;
      } catch (error) {
        lastError = error as Error;

        // 检查是否为过期锁
        if (await this.isLockStale()) {
          await this.forceRelease();
          continue;
        }

        // 等待后重试
        await this.sleep(this.options.retryDelay);
      }
    }

    throw new Error(
      `Failed to acquire checkpoint lock after ${this.options.retries} retries: ${lastError?.message}`
    );
  }

  /**
   * 尝试获取锁
   */
  private async tryAcquire(): Promise<void> {
    const lockInfo: LockInfo = {
      pid: process.pid,
      hostname: this.getHostname(),
      timestamp: Date.now(),
    };

    // 确保目录存在
    const lockDir = path.dirname(this.lockPath);
    await fs.mkdir(lockDir, { recursive: true });

    // 使用 exclusive 标志创建锁文件
    const handle = await fs.open(this.lockPath, 'wx');
    try {
      await handle.writeFile(JSON.stringify(lockInfo, null, 2));
    } finally {
      await handle.close();
    }
  }

  /**
   * 释放锁
   */
  async release(): Promise<void> {
    if (!this.locked) {
      return;
    }

    try {
      // 验证锁是否属于当前进程
      const lockInfo = await this.readLockInfo();
      if (lockInfo && lockInfo.pid === process.pid) {
        await fs.unlink(this.lockPath);
      }
    } catch {
      // 忽略错误
    } finally {
      this.locked = false;
    }
  }

  /**
   * 强制释放锁（用于清理过期锁）
   */
  private async forceRelease(): Promise<void> {
    try {
      await fs.unlink(this.lockPath);
    } catch {
      // 忽略错误
    }
  }

  /**
   * 检查锁是否过期
   */
  private async isLockStale(): Promise<boolean> {
    try {
      const lockInfo = await this.readLockInfo();
      if (!lockInfo) {
        return true;
      }

      const age = Date.now() - lockInfo.timestamp;
      return age > this.options.stale;
    } catch {
      return true;
    }
  }

  /**
   * 读取锁信息
   */
  private async readLockInfo(): Promise<LockInfo | null> {
    try {
      const content = await fs.readFile(this.lockPath, 'utf-8');
      return JSON.parse(content) as LockInfo;
    } catch {
      return null;
    }
  }

  /**
   * 获取主机名
   */
  private getHostname(): string {
    try {
      return require('os').hostname();
    } catch {
      return 'unknown';
    }
  }

  /**
   * 等待
   */
  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
   * 带锁执行操作
   */
  async withLock<T>(fn: () => Promise<T>): Promise<T> {
    await this.acquire();
    try {
      return await fn();
    } finally {
      await this.release();
    }
  }

  /**
   * 检查是否已锁定
   */
  isLocked(): boolean {
    return this.locked;
  }

  /**
   * 检查锁文件是否存在
   */
  async lockExists(): Promise<boolean> {
    try {
      await fs.access(this.lockPath);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * 获取锁持有者信息
   */
  async getLockHolder(): Promise<LockInfo | null> {
    return this.readLockInfo();
  }
}

/**
 * 创建检查点锁实例
 */
export function createCheckpointLock(
  shadowGitDir: string,
  options?: LockOptions
): CheckpointLock {
  return new CheckpointLock(shadowGitDir, options);
}
