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

class GitHubIntegrationService {
  constructor() {
    this.oauthService = new GitHubOAuthService();
    this.fileStorageService = new FileStorageService();
    
    // 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
  parseGitHubUrl(url) {
    const regex = /github\.com\/([^\/]+)\/([^\/]+)(?:\/tree\/([^\/]+))?/;
    const match = url.match(regex);
    
    if (!match) {
      throw new Error('Invalid GitHub repository URL');
    }

    return {
      owner: match[1],
      repo: match[2].replace('.git', ''),
      branch: match[3] || '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'
        };
      }
      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) {
    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'
      });
      
      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;
  }

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

  // Download repository files locally and store in database
  async downloadRepositoryWithStorage(owner, repo, branch, repositoryId) {
    const targetDir = path.join(
      process.env.ATTACHED_REPOS_DIR || '/tmp/attached-repos',
      `${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
      );
      
      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;
      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 || '/tmp/attached-repos',
      `${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;
