/**
 * Git Utilities
 * Handles git operations for version management
 */

import { execSync } from 'child_process';



export interface GitStatus {
  isRepo: boolean;
  isClean: boolean;
  currentBranch: string;
  uncommittedChanges: string[];
}

export interface GitVersion {
  tag: string;
  commit: string;
  date: string;
  message: string;
}

export class GitError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'GitError';
  }
}

export class Git {
  private cwd: string;

  constructor(workingDirectory: string = process.cwd()) {
    this.cwd = workingDirectory;
  }

  /**
   * Execute a git command
   */
  private exec(command: string): string {
    try {
      return execSync(command, {
        cwd: this.cwd,
        encoding: 'utf-8',
        stdio: ['pipe', 'pipe', 'pipe'],
      }).trim();
    } catch (error: any) {
      throw new GitError(`Git command failed: ${error.message}`);
    }
  }

  /**
   * Check if current directory is a git repository
   */
  isGitRepo(): boolean {
    try {
      this.exec('git rev-parse --git-dir');
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Get git status information
   */
  getStatus(): GitStatus {
    if (!this.isGitRepo()) {
      return {
        isRepo: false,
        isClean: false,
        currentBranch: '',
        uncommittedChanges: [],
      };
    }

    const currentBranch = this.exec('git rev-parse --abbrev-ref HEAD');
    const statusOutput = this.exec('git status --porcelain');
    const uncommittedChanges = statusOutput
      .split('\n')
      .filter(line => line.trim().length > 0);

    return {
      isRepo: true,
      isClean: uncommittedChanges.length === 0,
      currentBranch,
      uncommittedChanges,
    };
  }

  /**
   * Check if repository is clean (no uncommitted changes)
   */
  isClean(): boolean {
    const status = this.getStatus();
    return status.isClean;
  }

  /**
   * Get current commit hash
   */
  getCurrentCommit(): string {
    return this.exec('git rev-parse HEAD');
  }

  /**
   * Create a git tag
   */
  createTag(tagName: string, message: string): void {
    if (!this.isGitRepo()) {
      throw new GitError('Not a git repository');
    }

    // Check if tag already exists
    try {
      this.exec(`git rev-parse ${tagName}`);
      throw new GitError(`Tag '${tagName}' already exists`);
    } catch (error) {
      if (error instanceof GitError && error.message.includes('already exists')) {
        throw error;
      }
      // Tag doesn't exist, continue
    }

    this.exec(`git tag -a ${tagName} -m "${message}"`);
  }

  /**
   * List all tags (versions)
   */
  listTags(): GitVersion[] {
    if (!this.isGitRepo()) {
      return [];
    }

    try {
      const output = this.exec("git tag -l --format='%(refname:short)|%(objectname:short)|%(creatordate:short)|%(subject)'");

      if (!output) {
        return [];
      }

      return output.split('\n')
        .filter(line => line.trim().length > 0)
        .map(line => {
          const [tag, commit, date, message] = line.split('|');
          return { tag, commit, date, message };
        })
        .reverse(); // Most recent first
    } catch {
      return [];
    }
  }

  /**
   * Get a specific tag
   */
  getTag(tagName: string): GitVersion | null {
    try {
      const output = this.exec(`git tag -l ${tagName} --format='%(refname:short)|%(objectname:short)|%(creatordate:short)|%(subject)'`);

      if (!output) {
        return null;
      }

      const [tag, commit, date, message] = output.split('|');
      return { tag, commit, date, message };
    } catch {
      return null;
    }
  }

  /**
   * Checkout a specific tag
   */
  checkout(tagName: string): void {
    if (!this.isGitRepo()) {
      throw new GitError('Not a git repository');
    }

    if (!this.isClean()) {
      throw new GitError('Repository has uncommitted changes. Commit or stash them first.');
    }

    // Check if tag exists
    const tag = this.getTag(tagName);
    if (!tag) {
      throw new GitError(`Tag '${tagName}' does not exist`);
    }

    this.exec(`git checkout ${tagName}`);
  }

  /**
   * Get file content from a specific tag
   */
  getFileAtTag(tagName: string, filePath: string): string | null {
    if (!this.isGitRepo()) {
      return null;
    }

    try {
      return this.exec(`git show ${tagName}:${filePath}`);
    } catch {
      return null;
    }
  }

  /**
   * Create a commit with all changes
   */
  commit(message: string): void {
    if (!this.isGitRepo()) {
      throw new GitError('Not a git repository');
    }

    // Stage all changes
    this.exec('git add .');

    // Check if there's anything to commit
    if (this.isClean()) {
      throw new GitError('No changes to commit');
    }

    // Create commit
    this.exec(`git commit -m "${message}"`);
  }

  /**
   * Get diff between two tags
   */
  diff(fromTag: string, toTag: string): string {
    if (!this.isGitRepo()) {
      throw new GitError('Not a git repository');
    }

    return this.exec(`git diff ${fromTag}..${toTag} --stat`);
  }

  /**
   * Validate semver format
   */
  static isValidSemver(version: string): boolean {
    const semverRegex = /^(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
    return semverRegex.test(version);
  }

  /**
   * Compare two semver versions
   * Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
   */
  static compareSemver(v1: string, v2: string): number {
    const parse = (v: string) => {
      const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
      if (!match) return [0, 0, 0];
      return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
    };

    const [major1, minor1, patch1] = parse(v1);
    const [major2, minor2, patch2] = parse(v2);

    if (major1 !== major2) return major1 > major2 ? 1 : -1;
    if (minor1 !== minor2) return minor1 > minor2 ? 1 : -1;
    if (patch1 !== patch2) return patch1 > patch2 ? 1 : -1;
    return 0;
  }

  /**
   * Get the latest version tag
   */
  getLatestVersion(): string | null {
    const tags = this.listTags();
    const versionTags = tags.filter(t => Git.isValidSemver(t.tag));

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

    return versionTags[0].tag;
  }
}
