// services/git-repo.service.js
const fs = require('fs');
const path = require('path');
const { exec, execFile } = require('child_process');

class GitRepoService {
  constructor() {
    this.baseDir = process.env.ATTACHED_REPOS_DIR || '/tmp/attached-repos';
  }

  getLocalRepoPath(owner, repo, branch) {
    return path.join(this.baseDir, `${owner}__${repo}__${branch}`);
  }

  async ensureDirectory(dirPath) {
    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true });
    }
  }

  runGitCommand(cwd, command) {
    return new Promise((resolve, reject) => {
      try {
        if (!fs.existsSync(cwd)) {
          return reject(new Error(`Working directory not found: ${cwd}`));
        }
      } catch (_) {
        return reject(new Error(`Invalid working directory: ${cwd}`));
      }
      // Make git non-interactive to avoid terminal credential prompts
      const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
      exec(command, { cwd, maxBuffer: 1024 * 1024 * 64, env }, (error, stdout, stderr) => {
        if (error) {
          const details = [`cmd: ${command}`, `cwd: ${cwd}`, stderr ? `stderr: ${stderr}` : ''].filter(Boolean).join('\n');
          return reject(new Error((stderr && stderr.trim()) || `${error.message}\n${details}`));
        }
        resolve(stdout.trim());
      });
    });
  }

  runGit(cwd, args) {
    return new Promise((resolve, reject) => {
      try {
        if (!fs.existsSync(cwd)) {
          return reject(new Error(`Working directory not found: ${cwd}`));
        }
      } catch (_) {
        return reject(new Error(`Invalid working directory: ${cwd}`));
      }
      const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
      execFile('git', args, { cwd, maxBuffer: 1024 * 1024 * 64, env }, (error, stdout, stderr) => {
        if (error) {
          const details = [`git ${args.join(' ')}`, `cwd: ${cwd}`, stderr ? `stderr: ${stderr}` : ''].filter(Boolean).join('\n');
          return reject(new Error((stderr && stderr.trim()) || `${error.message}\n${details}`));
        }
        resolve(stdout.trim());
      });
    });
  }

  async cloneIfMissing(owner, repo, branch) {
    const repoPath = this.getLocalRepoPath(owner, repo, branch);
    await this.ensureDirectory(path.dirname(repoPath));
    if (!fs.existsSync(repoPath) || !fs.existsSync(path.join(repoPath, '.git'))) {
      const cloneUrl = `https://github.com/${owner}/${repo}.git`;
      await this.runGit(path.dirname(repoPath), ['clone', '--depth', '1', '-b', branch, cloneUrl, path.basename(repoPath)]);
    }
    return repoPath;
  }

  async cloneIfMissingWithHost(owner, repo, branch, host) {
    const repoPath = this.getLocalRepoPath(owner, repo, branch);
    await this.ensureDirectory(path.dirname(repoPath));
    if (!fs.existsSync(repoPath) || !fs.existsSync(path.join(repoPath, '.git'))) {
      const normalizedHost = (host || 'github.com').replace(/^https?:\/\//, '').replace(/\/$/, '');
      const cloneUrl = `https://${normalizedHost}/${owner}/${repo}.git`;
      await this.runGit(path.dirname(repoPath), ['clone', '--depth', '1', '-b', branch, cloneUrl, path.basename(repoPath)]);
    }
    return repoPath;
  }

  async getHeadSha(repoPath) {
    try {
      const sha = await this.runGit(repoPath, ['rev-parse', 'HEAD']);
      return sha;
    } catch (_) {
      return null;
    }
  }

  async fetchAndFastForward(repoPath, branch) {
    const beforeSha = await this.getHeadSha(repoPath);
    await this.runGit(repoPath, ['fetch', '--all', '--prune']);
    await this.runGit(repoPath, ['checkout', branch]);
    await this.runGit(repoPath, ['pull', '--ff-only', 'origin', branch]);
    const afterSha = await this.getHeadSha(repoPath);
    return { beforeSha, afterSha };
  }

  async cloneIfMissingWithAuth(owner, repo, branch, host, token, tokenType = 'oauth2') {
    const repoPath = this.getLocalRepoPath(owner, repo, branch);
    await this.ensureDirectory(path.dirname(repoPath));
    if (!fs.existsSync(repoPath) || !fs.existsSync(path.join(repoPath, '.git'))) {
      const normalizedHost = (host || 'github.com').replace(/^https?:\/\//, '').replace(/\/$/, '');
      let cloneUrl = `https://${normalizedHost}/${owner}/${repo}.git`;
      if (token) {
        if (tokenType === 'oauth2') {
          // Many providers accept oauth2:<token>@host
          cloneUrl = `https://oauth2:${token}@${normalizedHost}/${owner}/${repo}.git`;
        } else if (tokenType === 'bearer') {
          // Use extraheader auth pattern
          await this.runGit(path.dirname(repoPath), ['-c', `http.extraheader=Authorization: Bearer ${token}`, 'clone', '--depth', '1', '-b', branch, cloneUrl, path.basename(repoPath)]);
          return repoPath;
        }
      }
      await this.runGit(path.dirname(repoPath), ['clone', '--depth', '1', '-b', branch, cloneUrl, path.basename(repoPath)]);
    }
    return repoPath;
  }

  async getDiff(repoPath, fromSha, toSha, options = { patch: true }) {
    const range = fromSha && toSha ? `${fromSha}..${toSha}` : toSha ? `${toSha}^..${toSha}` : '';
    const mode = options.patch ? '--patch' : '--name-status';
    const args = ['diff', mode];
    if (range) args.push(range);
    const output = await this.runGit(repoPath, args);
    return output;
  }

  async getChangedFilesSince(repoPath, sinceSha) {
    const output = await this.runGit(repoPath, ['diff', '--name-status', `${sinceSha}..HEAD`]);
    const lines = output.split('\n').filter(Boolean);
    return lines.map(line => {
      const [status, filePath] = line.split(/\s+/, 2);
      return { status, filePath };
    });
  }
}

module.exports = GitRepoService;


