// Updated github-integration.js service
const { Octokit } = require('@octokit/rest');
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const parseGitHubUrl = require('parse-github-url');
const GitHubOAuthService = require('./github-oauth');
const FileStorageService = require('./file-storage.service');
const GitRepoService = require('./git-repo.service');

class GitHubIntegrationService {
  constructor() {
    this.oauthService = new GitHubOAuthService();
    this.fileStorageService = new FileStorageService();
    this.gitRepoService = new GitRepoService();
    
    // Default unauthenticated instance
    this.octokit = new Octokit({
      userAgent: 'CodeNuk-GitIntegration/1.0.0',
    });
  }

  // Get authenticated Octokit instance
  async getAuthenticatedOctokit() {
    return await this.oauthService.getAuthenticatedOctokit();
  }

  // Extract owner, repo, and branch from GitHub URL using parse-github-url library
  parseGitHubUrl(url) {
    if (!url || typeof url !== 'string') {
      throw new Error('URL must be a non-empty string');
    }

    // Normalize the URL first
    let normalizedUrl = url.trim();
    
    // Handle URLs without protocol
    if (!normalizedUrl.startsWith('http://') && !normalizedUrl.startsWith('https://') && !normalizedUrl.startsWith('git@')) {
      normalizedUrl = 'https://' + normalizedUrl;
    }
    
    // Handle SSH format: git@github.com:owner/repo.git
    if (normalizedUrl.startsWith('git@github.com:')) {
      normalizedUrl = normalizedUrl.replace('git@github.com:', 'https://github.com/');
    }
    
    // Handle git+https format: git+https://github.com/owner/repo.git
    if (normalizedUrl.startsWith('git+https://') || normalizedUrl.startsWith('git+http://')) {
      normalizedUrl = normalizedUrl.replace('git+', '');
    }

    // Validate that it's a GitHub URL before parsing
    if (!normalizedUrl.includes('github.com')) {
      throw new Error(`Invalid GitHub repository URL: ${url}`);
    }

    // Clean URL by removing query parameters and fragments for parsing
    const cleanUrl = normalizedUrl.split('?')[0].split('#')[0];

    // Use the parse-github-url library to parse the URL
    const parsed = parseGitHubUrl(cleanUrl);
    
    if (!parsed || !parsed.owner || !parsed.name) {
      throw new Error(`Invalid GitHub repository URL: ${url}`);
    }

    // Additional validation: reject URLs with invalid paths
    const urlWithoutQuery = normalizedUrl.split('?')[0].split('#')[0];
    const pathAfterRepo = urlWithoutQuery.split(/github\.com\/[^\/]+\/[^\/]+/)[1];
    if (pathAfterRepo && pathAfterRepo.length > 0) {
      const validPaths = ['/tree/', '/blob/', '/commit/', '/pull/', '/issue', '/archive/', '/releases', '/actions', '/projects', '/wiki', '/settings', '/security', '/insights', '/pulse', '/graphs', '/network', '/compare'];
      const hasValidPath = validPaths.some(path => pathAfterRepo.startsWith(path));
      if (!hasValidPath) {
        throw new Error(`Invalid GitHub repository URL: ${url}`);
      }
    }

    // Extract branch information
    let branch = parsed.branch;
    
    // Handle special cases for branch extraction
    if (branch) {
      // For archive URLs, remove .zip or .tar.gz extensions
      branch = branch.replace(/\.(zip|tar\.gz|tar)$/, '');
      
      // For blob URLs, the branch might be followed by a path, take only the first part
      branch = branch.split('/')[0];
      
      // For commit/PR/issue URLs, don't treat the ID as a branch
      if (normalizedUrl.includes('/commit/') || normalizedUrl.includes('/pull/') || normalizedUrl.includes('/issue')) {
        branch = 'main'; // Default to main for these cases
      }
    }

    // Validate owner and repo names (GitHub naming rules)
    if (!/^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$/.test(parsed.owner)) {
      throw new Error(`Invalid GitHub owner name: ${parsed.owner}`);
    }
    
    if (!/^[a-zA-Z0-9]([a-zA-Z0-9\-\._]*[a-zA-Z0-9])?$/.test(parsed.name)) {
      throw new Error(`Invalid GitHub repository name: ${parsed.name}`);
    }

    return {
      owner: parsed.owner,
      repo: parsed.name,
      branch: branch || 'main'
    };
  }

  // Check repository access and type
  async checkRepositoryAccess(owner, repo) {
    try {
      const octokit = await this.getAuthenticatedOctokit();
      const { data } = await octokit.repos.get({ owner, repo });
      
      return {
        exists: true,
        isPrivate: data.private,
        hasAccess: true,
        requiresAuth: data.private
      };
    } catch (error) {
      if (error.status === 404) {
        return {
          exists: false,
          isPrivate: null,
          hasAccess: false,
          requiresAuth: true,
          error: 'Repository not found or requires authentication'
        };
      }
      
      // Handle authentication errors
      if (error.status === 401 || error.message.includes('token has expired') || error.message.includes('authenticate with GitHub')) {
        return {
          exists: null,
          isPrivate: null,
          hasAccess: false,
          requiresAuth: true,
          error: 'GitHub authentication required or token expired',
          authError: true
        };
      }
      
      throw error;
    }
  }

  // Check repository access with user-specific tokens
  async checkRepositoryAccessWithUser(owner, repo, userId) {
    try {
      // First try to find a token that can access this repository
      const token = await this.oauthService.findTokenForRepository(userId, owner, repo);
      
      if (token) {
        // We found a token that can access this repository
        const octokit = new Octokit({ auth: token.access_token });
        const { data } = await octokit.repos.get({ owner, repo });
        
        return {
          exists: true,
          isPrivate: data.private,
          hasAccess: true,
          requiresAuth: data.private,
          github_username: token.github_username,
          token_id: token.id
        };
      }
      
      // No token found - try unauthenticated access first to check if it's public
      try {
        const unauthenticatedOctokit = new Octokit({
          userAgent: 'CodeNuk-GitIntegration/1.0.0',
        });
        const { data } = await unauthenticatedOctokit.repos.get({ owner, repo });
        
        // Repository exists and is public
        return {
          exists: true,
          isPrivate: false,
          hasAccess: true,
          requiresAuth: false,
          github_username: null,
          token_id: null
        };
      } catch (unauthenticatedError) {
        if (unauthenticatedError.status === 404) {
          // Repository truly doesn't exist
          return {
            exists: false,
            isPrivate: null,
            hasAccess: false,
            requiresAuth: false,
            error: 'Repository not found'
          };
        } else if (unauthenticatedError.status === 401 || unauthenticatedError.status === 403) {
          // Repository exists but requires authentication (private) - generate auth URL
          const authUrl = await this.oauthService.generateAuthUrl(userId);
          return {
            exists: true,
            isPrivate: true,
            hasAccess: false,
            requiresAuth: true,
            error: 'Private repository requires authentication',
            authError: false,
            auth_url: authUrl
          };
        }
        
        // Other error - treat as private repository requiring auth - generate auth URL
        const authUrl = await this.oauthService.generateAuthUrl(userId);
        return {
          exists: null,
          isPrivate: null,
          hasAccess: false,
          requiresAuth: true,
          error: 'Repository requires authentication',
          authError: false,
          auth_url: authUrl
        };
      }
      
    } catch (error) {
      // Handle authentication errors - generate auth URL
      if (error.status === 401 || error.message.includes('token has expired') || error.message.includes('authenticate with GitHub')) {
        const authUrl = await this.oauthService.generateAuthUrl(userId);
        return {
          exists: null,
          isPrivate: null,
          hasAccess: false,
          requiresAuth: true,
          error: 'GitHub authentication required or token expired',
          authError: true,
          auth_url: authUrl
        };
      }
      
      throw error;
    }
  }

  // Get repository information from GitHub
  async fetchRepositoryMetadata(owner, repo) {
    const octokit = await this.getAuthenticatedOctokit();
    
    const safe = async (fn, fallback) => {
      try { 
        return await fn(); 
      } catch (error) { 
        console.warn(`API call failed: ${error.message}`);
        return fallback; 
      }
    };

    const repoData = await safe(
      async () => (await octokit.repos.get({ owner, repo })).data,
      {}
    );

    const languages = await safe(
      async () => (await octokit.repos.listLanguages({ owner, repo })).data,
      {}
    );

    const topics = await safe(
      async () => (await octokit.repos.getAllTopics({ owner, repo })).data?.names || [],
      []
    );

    return {
      full_name: repoData.full_name || `${owner}/${repo}`,
      description: repoData.description || null,
      language: repoData.language || null,
      topics,
      languages,
      visibility: repoData.private ? 'private' : 'public',
      stargazers_count: repoData.stargazers_count || 0,
      forks_count: repoData.forks_count || 0,
      default_branch: repoData.default_branch || 'main',
      size: repoData.size || 0,
      updated_at: repoData.updated_at || new Date().toISOString()
    };
  }

  // Analyze codebase structure
  async analyzeCodebase(owner, repo, branch, isPublicRepo = false) {
    try {
      // Use appropriate octokit instance based on repository type
      let octokit;
      if (isPublicRepo) {
        // For public repos, use unauthenticated octokit
        octokit = new Octokit({
          userAgent: 'CodeNuk-GitIntegration/1.0.0',
        });
      } else {
        // For private repos, use authenticated octokit
        octokit = await this.getAuthenticatedOctokit();
      }
      
      // Get the commit SHA for the branch
      const { data: ref } = await octokit.git.getRef({
        owner,
        repo,
        ref: `heads/${branch}`
      });
      
      const commitSha = ref.object.sha;
      
      // Get the tree recursively
      const { data: tree } = await octokit.git.getTree({
        owner,
        repo,
        tree_sha: commitSha,
        recursive: 'true'
      });
      
      const analysis = {
        total_files: 0,
        total_size: 0,
        languages: {},
        file_types: {},
        directories: [],
        last_commit: commitSha,
        branch: branch
      };
      
      tree.tree.forEach(item => {
        if (item.type === 'blob') {
          analysis.total_files++;
          analysis.total_size += item.size || 0;
          
          const ext = path.extname(item.path).toLowerCase();
          analysis.file_types[ext] = (analysis.file_types[ext] || 0) + 1;
        } else if (item.type === 'tree') {
          analysis.directories.push(item.path);
        }
      });
      
      return analysis;
    } catch (error) {
      console.error('Error analyzing codebase:', error);
      return {
        error: error.message,
        total_files: 0,
        total_size: 0
      };
    }
  }

  // Update GitHub SHAs for files after processing
  async updateFileGitHubSHAs(repositoryId, fileMap) {
    try {
      const database = require('../config/database');
      for (const [relativePath, githubSha] of fileMap.entries()) {
        await database.query(
          'UPDATE repository_files SET github_sha = $1 WHERE repository_id = $2 AND relative_path = $3',
          [githubSha, repositoryId, relativePath]
        );
      }
    } catch (error) {
      console.warn('Error updating GitHub SHAs:', error.message);
    }
  }

  // Get repository storage information
  async getRepositoryStorage(repositoryId) {
    const database = require('../config/database');
    const query = `
      SELECT rs.*, 
             COUNT(DISTINCT rd.id) as directories_count,
             COUNT(rf.id) as files_count
      FROM repository_storage rs
      LEFT JOIN repository_directories rd ON rs.id = rd.storage_id
      LEFT JOIN repository_files rf ON rs.id = rf.storage_id
      WHERE rs.repository_id = $1
      GROUP BY rs.id
    `;
    
    const result = await database.query(query, [repositoryId]);
    return result.rows[0] || null;
  }

  // Ensure a GitHub webhook exists for the repository (uses OAuth token)
  async ensureRepositoryWebhook(owner, repo, callbackUrl) {
    try {
      const secret = process.env.GITHUB_WEBHOOK_SECRET;
      if (!callbackUrl) {
        console.warn('Webhook callbackUrl not provided; skipping webhook creation');
        return { created: false, reason: 'missing_callback_url' };
      }

      const octokit = await this.getAuthenticatedOctokit();

      // List existing hooks to avoid duplicates
      const { data: hooks } = await octokit.request('GET /repos/{owner}/{repo}/hooks', {
        owner,
        repo
      });

      const existing = hooks.find(h => h.config && h.config.url === callbackUrl);
      if (existing) {
        // Optionally ensure events include push
        if (!existing.events || !existing.events.includes('push')) {
          try {
            await octokit.request('PATCH /repos/{owner}/{repo}/hooks/{hook_id}', {
              owner,
              repo,
              hook_id: existing.id,
              events: Array.from(new Set([...(existing.events || []), 'push']))
            });
          } catch (e) {
            console.warn('Failed to update existing webhook events:', e.message);
          }
        }
        return { created: false, reason: 'exists', hook_id: existing.id };
      }

      // Create new webhook
      const createResp = await octokit.request('POST /repos/{owner}/{repo}/hooks', {
        owner,
        repo,
        config: {
          url: callbackUrl,
          content_type: 'json',
          secret: secret || undefined,
          insecure_ssl: '0'
        },
        events: ['push'],
        active: true
      });

      return { created: true, hook_id: createResp.data.id };
    } catch (error) {
      // Common cases: insufficient permissions, private repo without correct scope
      console.warn('ensureRepositoryWebhook failed:', error.status, error.message);
      return { created: false, error: error.message };
    }
  }

  // Git-based: clone or update local repo and re-index into DB
  async syncRepositoryWithGit(owner, repo, branch, repositoryId, isPublicRepo = false) {
    const database = require('../config/database');
    const localPath = this.gitRepoService.getLocalRepoPath(owner, repo, branch);
    let storageRecord = null;

    try {
      await this.gitRepoService.ensureDirectory(path.dirname(localPath));

      // Initialize storage record as downloading
      storageRecord = await this.fileStorageService.initializeRepositoryStorage(
        repositoryId,
        localPath
      );

      // Clone if missing (prefer authenticated HTTPS with OAuth token for private repos, public for public repos)
      let repoPath = null;
      
      if (isPublicRepo) {
        // For public repos, try unauthenticated clone first
        try {
          repoPath = await this.gitRepoService.cloneIfMissing(owner, repo, branch);
        } catch (error) {
          console.warn(`Failed to clone public repo without auth: ${error.message}`);
          // Fallback to authenticated clone if available
          try {
            const tokenRecord = await this.oauthService.getToken();
            if (tokenRecord?.access_token) {
              repoPath = await this.gitRepoService.cloneIfMissingWithAuth(
                owner,
                repo,
                branch,
                'github.com',
                tokenRecord.access_token,
                'oauth2'
              );
            }
          } catch (_) {}
        }
      } else {
        // For private repos, try authenticated clone first
        try {
          const tokenRecord = await this.oauthService.getToken();
          if (tokenRecord?.access_token) {
            repoPath = await this.gitRepoService.cloneIfMissingWithAuth(
              owner,
              repo,
              branch,
              'github.com',
              tokenRecord.access_token,
              'oauth2'
            );
          }
        } catch (_) {}
        
        // Fallback to unauthenticated clone (will likely fail for private repos)
        if (!repoPath) {
          repoPath = await this.gitRepoService.cloneIfMissing(owner, repo, branch);
        }
      }
      
      if (!repoPath) {
        throw new Error('Failed to clone repository');
      }
      
      const beforeSha = await this.gitRepoService.getHeadSha(repoPath);
      const { afterSha } = await this.gitRepoService.fetchAndFastForward(repoPath, branch);

      // Index filesystem into DB
      await this.fileStorageService.processDirectoryStructure(
        storageRecord.id,
        repositoryId,
        repoPath
      );

      const finalStorage = await this.fileStorageService.completeRepositoryStorage(storageRecord.id);

      // Persist last synced commit
      try {
        await database.query(
          'UPDATE github_repositories SET last_synced_commit_sha = $1, last_synced_at = NOW(), updated_at = NOW() WHERE id = $2',
          [afterSha || beforeSha || null, repositoryId]
        );
      } catch (_) {}

      return {
        success: true,
        targetDir: repoPath,
        beforeSha,
        afterSha: afterSha || beforeSha,
        storage: finalStorage
      };
    } catch (error) {
      if (storageRecord) {
        await this.fileStorageService.markStorageFailed(storageRecord.id, error.message);
      }
      return { success: false, error: error.message };
    }
  }

  // Git-based: get unified diff between two SHAs in local repo
  async getRepositoryDiff(owner, repo, branch, fromSha, toSha) {
    // Ensure local repo exists and is up to date; handle main/master mismatch gracefully
    const preferredBranch = branch || 'main';
    const alternateBranch = preferredBranch === 'main' ? 'master' : 'main';

    let repoPath = this.gitRepoService.getLocalRepoPath(owner, repo, preferredBranch);
    try {
      // Try to ensure repo exists for the preferred branch
      try {
        const tokenRecord = await this.oauthService.getToken().catch(() => null);
        if (tokenRecord?.access_token) {
          repoPath = await this.gitRepoService.cloneIfMissingWithAuth(owner, repo, preferredBranch, 'github.com', tokenRecord.access_token, 'oauth2');
        } else {
          repoPath = await this.gitRepoService.cloneIfMissing(owner, repo, preferredBranch);
        }
      } catch (cloneErr) {
        // If the branch doesn't exist (e.g., refs/heads not found), try the alternate branch
        try {
          const tokenRecordAlt = await this.oauthService.getToken().catch(() => null);
          repoPath = tokenRecordAlt?.access_token
            ? await this.gitRepoService.cloneIfMissingWithAuth(owner, repo, alternateBranch, 'github.com', tokenRecordAlt.access_token, 'oauth2')
            : await this.gitRepoService.cloneIfMissing(owner, repo, alternateBranch);
        } catch (_) {
          // Fall through; we'll try to use any existing local copy next
        }
      }

      // If a local repo exists for alternate branch, prefer that to avoid failures
      const fs = require('fs');
      const altPath = this.gitRepoService.getLocalRepoPath(owner, repo, alternateBranch);
      if ((!fs.existsSync(repoPath) || !fs.existsSync(require('path').join(repoPath, '.git'))) && fs.existsSync(altPath)) {
        repoPath = altPath;
      }

      // Update and checkout target ref if possible
      try {
        await this.gitRepoService.fetchAndFastForward(repoPath, preferredBranch);
      } catch (_) {
        // If checkout fails for preferred branch, attempt alternate
        try { await this.gitRepoService.fetchAndFastForward(repoPath, alternateBranch); } catch (_) {}
      }

      const patch = await this.gitRepoService.getDiff(repoPath, fromSha || null, toSha || 'HEAD', { patch: true });
      return patch;
    } catch (error) {
      // Surface a clearer error including both attempted paths
      const attempted = [this.gitRepoService.getLocalRepoPath(owner, repo, preferredBranch), this.gitRepoService.getLocalRepoPath(owner, repo, alternateBranch)].join(' | ');
      throw new Error(`${error.message} (attempted paths: ${attempted})`);
    }
  }

  // Git-based: list changed files since a SHA
  async getRepositoryChangesSince(owner, repo, branch, sinceSha) {
    const preferredBranch = branch || 'main';
    const alternateBranch = preferredBranch === 'main' ? 'master' : 'main';

    let repoPath = this.gitRepoService.getLocalRepoPath(owner, repo, preferredBranch);
    try {
      // Ensure repo exists similarly to diff flow
      try {
        const tokenRecord = await this.oauthService.getToken().catch(() => null);
        if (tokenRecord?.access_token) {
          repoPath = await this.gitRepoService.cloneIfMissingWithAuth(owner, repo, preferredBranch, 'github.com', tokenRecord.access_token, 'oauth2');
        } else {
          repoPath = await this.gitRepoService.cloneIfMissing(owner, repo, preferredBranch);
        }
      } catch (_) {
        try {
          const tokenRecordAlt = await this.oauthService.getToken().catch(() => null);
          repoPath = tokenRecordAlt?.access_token
            ? await this.gitRepoService.cloneIfMissingWithAuth(owner, repo, alternateBranch, 'github.com', tokenRecordAlt.access_token, 'oauth2')
            : await this.gitRepoService.cloneIfMissing(owner, repo, alternateBranch);
        } catch (_) {}
      }

      const fs = require('fs');
      const altPath = this.gitRepoService.getLocalRepoPath(owner, repo, alternateBranch);
      if ((!fs.existsSync(repoPath) || !fs.existsSync(require('path').join(repoPath, '.git'))) && fs.existsSync(altPath)) {
        repoPath = altPath;
      }

      try {
        await this.gitRepoService.fetchAndFastForward(repoPath, preferredBranch);
      } catch (_) {
        try { await this.gitRepoService.fetchAndFastForward(repoPath, alternateBranch); } catch (_) {}
      }

      const files = await this.gitRepoService.getChangedFilesSince(repoPath, sinceSha);
      return files;
    } catch (error) {
      const attempted = [this.gitRepoService.getLocalRepoPath(owner, repo, preferredBranch), this.gitRepoService.getLocalRepoPath(owner, repo, alternateBranch)].join(' | ');
      throw new Error(`${error.message} (attempted paths: ${attempted})`);
    }
  }

  // Clean up repository storage
  async cleanupRepositoryStorage(repositoryId) {
    return await this.fileStorageService.cleanupRepositoryStorage(repositoryId);
  }

  // Try git-based sync first, fall back to GitHub API download on failure
  async syncRepositoryWithFallback(owner, repo, branch, repositoryId, isPublicRepo = false) {
    // First attempt: full git clone/fetch and index
    const gitResult = await this.syncRepositoryWithGit(owner, repo, branch, repositoryId, isPublicRepo);
    if (gitResult && gitResult.success) {
      return { method: 'git', ...gitResult };
    }

    // Fallback: API-based download and storage
    const apiResult = await this.downloadRepositoryWithStorage(owner, repo, branch, repositoryId, isPublicRepo);
    if (apiResult && apiResult.success) {
      return { method: 'api', ...apiResult, git_error: gitResult?.error };
    }

    return { success: false, error: apiResult?.error || gitResult?.error || 'Unknown sync failure' };
  }

  // Download repository files locally and store in database
  async downloadRepositoryWithStorage(owner, repo, branch, repositoryId, isPublicRepo = false) {
    const targetDir = path.join(
      process.env.ATTACHED_REPOS_DIR,
      `${owner}__${repo}__${branch}`
    );
    
    // Create target directory
    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true });
    }
    
    let storageRecord = null;
    
    try {
      // Initialize storage record
      storageRecord = await this.fileStorageService.initializeRepositoryStorage(
        repositoryId, 
        targetDir
      );
      
      // Use appropriate octokit instance based on repository type
      let octokit;
      if (isPublicRepo) {
        // For public repos, use unauthenticated octokit
        octokit = new Octokit({
          userAgent: 'CodeNuk-GitIntegration/1.0.0',
        });
      } else {
        // For private repos, use authenticated octokit
        octokit = await this.getAuthenticatedOctokit();
      }
      
      // Get the commit SHA for the branch
      const { data: ref } = await octokit.git.getRef({
        owner,
        repo,
        ref: `heads/${branch}`
      });
      
      const commitSha = ref.object.sha;
      
      // Get the tree recursively
      const { data: tree } = await octokit.git.getTree({
        owner,
        repo,
        tree_sha: commitSha,
        recursive: 'true'
      });
      
      let filesWritten = 0;
      let totalBytes = 0;
      const fileMap = new Map(); // Map to store GitHub SHA for files
      
      // Process each file
      for (const item of tree.tree) {
        if (item.type === 'blob') {
          try {
            const { data: blob } = await octokit.git.getBlob({
              owner,
              repo,
              file_sha: item.sha
            });
            
            const filePath = path.join(targetDir, item.path);
            const fileDir = path.dirname(filePath);
            
            // Create directory if it doesn't exist
            if (!fs.existsSync(fileDir)) {
              fs.mkdirSync(fileDir, { recursive: true });
            }
            
            // Write file content
            const content = Buffer.from(blob.content, 'base64');
            fs.writeFileSync(filePath, content);
            
            // Store GitHub SHA for later use
            fileMap.set(item.path, item.sha);
            
            filesWritten++;
            totalBytes += content.length;
          } catch (error) {
            console.warn(`Failed to download file ${item.path}:`, error.message);
          }
        }
      }
      
      // Process directory structure and store in database
      console.log('Processing directory structure...');
      await this.fileStorageService.processDirectoryStructure(
        storageRecord.id,
        repositoryId,
        targetDir
      );
      
      // Update GitHub SHAs for files
      await this.updateFileGitHubSHAs(repositoryId, fileMap);
      
      // Complete storage process
      const finalStorage = await this.fileStorageService.completeRepositoryStorage(
        storageRecord.id
      );
      
      console.log(`Repository storage completed: ${finalStorage.total_files_count} files, ${finalStorage.total_directories_count} directories`);
      
      return {
        success: true,
        targetDir,
        files: filesWritten,
        bytes: totalBytes,
        storage: finalStorage
      };
      
    } catch (error) {
      console.error('Error downloading repository with storage:', error);
      
      if (storageRecord) {
        await this.fileStorageService.markStorageFailed(storageRecord.id, error.message);
      }
      
      return {
        success: false,
        error: error.message
      };
    }
  }

  // Legacy method - download repository files locally (backwards compatibility)
  async downloadRepository(owner, repo, branch) {
    const targetDir = path.join(
      process.env.ATTACHED_REPOS_DIR,
      `${owner}__${repo}__${branch}`
    );
    
    // Create target directory
    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true });
    }
    
    try {
      const octokit = await this.getAuthenticatedOctokit();
      
      // Get the commit SHA for the branch
      const { data: ref } = await octokit.git.getRef({
        owner,
        repo,
        ref: `heads/${branch}`
      });
      
      const commitSha = ref.object.sha;
      
      // Get the tree recursively
      const { data: tree } = await octokit.git.getTree({
        owner,
        repo,
        tree_sha: commitSha,
        recursive: 'true'
      });
      
      let filesWritten = 0;
      let totalBytes = 0;
      
      // Process each file
      for (const item of tree.tree) {
        if (item.type === 'blob') {
          try {
            const { data: blob } = await octokit.git.getBlob({
              owner,
              repo,
              file_sha: item.sha
            });
            
            const filePath = path.join(targetDir, item.path);
            const fileDir = path.dirname(filePath);
            
            // Create directory if it doesn't exist
            if (!fs.existsSync(fileDir)) {
              fs.mkdirSync(fileDir, { recursive: true });
            }
            
            // Write file content
            const content = Buffer.from(blob.content, 'base64');
            fs.writeFileSync(filePath, content);
            
            filesWritten++;
            totalBytes += content.length;
          } catch (error) {
            console.warn(`Failed to download file ${item.path}:`, error.message);
          }
        }
      }
      
      return {
        success: true,
        targetDir,
        files: filesWritten,
        bytes: totalBytes
      };
      
    } catch (error) {
      console.error('Error downloading repository:', error);
      return {
        success: false,
        error: error.message
      };
    }
  }
}

module.exports = GitHubIntegrationService;