// 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';
    this.initializeGitConfig();
  }

  initializeGitConfig() {
    // Set up git configuration to handle ownership issues
    const { exec } = require('child_process');
    const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
    
    // Configure git to be more permissive with ownership
    exec('git config --global --add safe.directory "*"', { env }, (error) => {
      if (error) {
        console.warn('Warning: Could not configure git safe directories:', error.message);
      } else {
        console.log('✅ Git safe directory configuration applied');
      }
    });
  }

  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`;
      // Full clone to include .git directory and complete history
      await this.runGit(path.dirname(repoPath), ['clone', '-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`;
      // Full clone to include .git directory and complete history
      await this.runGit(path.dirname(repoPath), ['clone', '-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 - Full clone to include .git directory
          await this.runGit(path.dirname(repoPath), ['-c', `http.extraheader=Authorization: Bearer ${token}`, 'clone', '-b', branch, cloneUrl, path.basename(repoPath)]);
          return repoPath;
        }
      }
      // Full clone to include .git directory and complete history
      await this.runGit(path.dirname(repoPath), ['clone', '-b', branch, cloneUrl, path.basename(repoPath)]);
    }
    return repoPath;
  }

  async getDiff(repoPath, fromSha, toSha, options = { patch: true }) {
    console.log(`🔍 Getting diff for repo: ${repoPath}`);
    console.log(`🔍 From SHA: ${fromSha}`);
    console.log(`🔍 To SHA: ${toSha}`);
    
    try {
      // Ensure both SHAs exist locally; if using shallow clone, fetch missing objects
      if (fromSha) {
        console.log(`🔍 Checking if fromSha exists: ${fromSha}`);
        try {
          await this.runGit(repoPath, ['cat-file', '-e', `${fromSha}^{commit}`]);
          console.log(`✅ From SHA exists locally`);
        } catch (error) {
          console.log(`⚠️  From SHA not found locally, fetching...`);
          try {
            await this.runGit(repoPath, ['fetch', '--depth', '200', 'origin', fromSha]);
            console.log(`✅ Fetched from SHA from origin`);
          } catch (fetchError) {
            console.warn(`⚠️  Failed to fetch from SHA: ${fetchError.message}`);
          }
        }
      }
      
      if (toSha && toSha !== 'HEAD') {
        console.log(`🔍 Checking if toSha exists: ${toSha}`);
        try {
          await this.runGit(repoPath, ['cat-file', '-e', `${toSha}^{commit}`]);
          console.log(`✅ To SHA exists locally`);
        } catch (error) {
          console.log(`⚠️  To SHA not found locally, fetching...`);
          try {
            await this.runGit(repoPath, ['fetch', '--depth', '200', 'origin', toSha]);
            console.log(`✅ Fetched to SHA from origin`);
          } catch (fetchError) {
            console.warn(`⚠️  Failed to fetch to SHA: ${fetchError.message}`);
          }
        }
      }
    } catch (error) {
      console.warn(`⚠️  Error during SHA verification: ${error.message}`);
    }

    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);
    
    console.log(`🔍 Running git command: git ${args.join(' ')}`);
    console.log(`🔍 Range: ${range}`);
    
    const output = await this.runGit(repoPath, args);
    console.log(`🔍 Git diff output length: ${output ? output.length : 0} characters`);
    
    return output;
  }

  async getChangedFilesSince(repoPath, sinceSha) {
      // Ensure SHA exists locally in case of shallow clone
      try {
        await this.runGit(repoPath, ['cat-file', '-e', `${sinceSha}^{commit}`]).catch(async () => { 
          await this.runGit(repoPath, ['fetch', '--depth', '200', 'origin', sinceSha]);  
        });  
      } catch (_) {}
    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;


