// Updated routes/github-integration.js
const express = require('express');
const router = express.Router();
const GitHubIntegrationService = require('../services/github-integration.service');
const GitHubOAuthService = require('../services/github-oauth');
const FileStorageService = require('../services/file-storage.service');
const database = require('../config/database');
const fs = require('fs');
const path = require('path');

const githubService = new GitHubIntegrationService();
const oauthService = new GitHubOAuthService();
const fileStorageService = new FileStorageService();

// Attach GitHub repository to template
router.post('/attach-repository', async (req, res) => {
  try {
    const { repository_url, branch_name } = req.body;
    const userId = req.headers['x-user-id'] || req.query.user_id || req.body.user_id || (req.user && (req.user.id || req.user.userId));

    // Validate input
    if (!repository_url) {
      return res.status(400).json({
        success: false,
        message: 'Repository URL is required'
      });
    }

    // Parse GitHub URL
    const { owner, repo, branch } = githubService.parseGitHubUrl(repository_url);

    // First, try to check if this is a public repository without authentication
    let isPublicRepo = false;
    let repositoryData = null;

    try {
      // Try to access the repository without authentication first (for public repos)
      const unauthenticatedOctokit = new (require('@octokit/rest')).Octokit({
        userAgent: 'CodeNuk-GitIntegration/1.0.0',
      });
      
      const { data: repoInfo } = await unauthenticatedOctokit.repos.get({ owner, repo });
      isPublicRepo = !repoInfo.private;
      repositoryData = {
        full_name: repoInfo.full_name,
        description: repoInfo.description,
        language: repoInfo.language,
        visibility: repoInfo.private ? 'private' : 'public',
        stargazers_count: repoInfo.stargazers_count,
        forks_count: repoInfo.forks_count,
        default_branch: repoInfo.default_branch,
        size: repoInfo.size,
        updated_at: repoInfo.updated_at
      };
      
      console.log(`✅ Repository ${owner}/${repo} is ${isPublicRepo ? 'public' : 'private'}`);
    } catch (error) {
      // IMPORTANT: GitHub returns 404 for private repos when unauthenticated.
      // Do NOT immediately return 404 here; instead continue to check auth and treat as potentially private.
      if (error.status && error.status !== 404) {
        // For non-404 errors (e.g., rate-limit, network), surface a meaningful message
        console.warn(`Unauthenticated access failed with status ${error.status}: ${error.message}`);
      }

      // If we can't access it without auth (including 404), it's likely private - check if user has auth
      console.log(`❌ Cannot access ${owner}/${repo} without authentication (status=${error.status || 'unknown'}), checking user auth...`);
      
      // Check if user has GitHub authentication
      let hasAuth = false;
      try {
        const authStatus = await oauthService.getAuthStatus();
        hasAuth = authStatus.connected;
        console.log(`🔐 User authentication status: ${hasAuth ? 'Connected' : 'Not connected'}`);
      } catch (authError) {
        console.log(`❌ Error checking auth status: ${authError.message}`);
        hasAuth = false;
      }

      // If user is not authenticated, first optimistically try to attach as PUBLIC via git clone.
      // If cloning fails (e.g., due to private permissions), then prompt OAuth.
      if (!hasAuth) {
        try {
          // Minimal metadata assuming public repo
          repositoryData = {
            full_name: `${owner}/${repo}`,
            description: null,
            language: null,
            visibility: 'public',
            stargazers_count: 0,
            forks_count: 0,
            default_branch: branch || branch_name || 'main',
            size: 0,
            updated_at: new Date().toISOString()
          };
          isPublicRepo = true;

          // Determine branch fallback
          let actualBranchLocal = repositoryData.default_branch || 'main';
          if (branch_name) actualBranchLocal = branch_name;

          // Store DB record before syncing (sync_status=syncing)
          const insertQueryPublic = `
            INSERT INTO github_repositories (
              repository_url, repository_name, owner_name, 
              branch_name, is_public, metadata, codebase_analysis, sync_status,
              requires_auth, user_id
            ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
            RETURNING *
          `;
          const insertValuesPublic = [
            repository_url,
            repo,
            owner,
            actualBranchLocal,
            true,
            JSON.stringify(repositoryData),
            JSON.stringify({ branch: actualBranchLocal, total_files: 0, total_size: 0 }),
            'syncing',
            false,
            userId || null
          ];
          const insertResPublic = await database.query(insertQueryPublic, insertValuesPublic);
          const repositoryRecordPublic = insertResPublic.rows[0];

          // Try to sync via git without auth (attempt preferred branch, then alternate if needed)
          let downloadResultPublic = await githubService.syncRepositoryWithFallback(
            owner, repo, actualBranchLocal, repositoryRecordPublic.id, true
          );
          if (!downloadResultPublic.success) {
            const altBranch = actualBranchLocal === 'main' ? 'master' : 'main';
            console.warn(`First public sync attempt failed on '${actualBranchLocal}', retrying with alternate branch '${altBranch}'...`);
            downloadResultPublic = await githubService.syncRepositoryWithFallback(
              owner, repo, altBranch, repositoryRecordPublic.id, true
            );
            if (downloadResultPublic.success) {
              // Persist branch switch
              await database.query('UPDATE github_repositories SET branch_name = $1, updated_at = NOW() WHERE id = $2', [altBranch, repositoryRecordPublic.id]);
              actualBranchLocal = altBranch;
            }
          }

          const finalSyncStatusPublic = downloadResultPublic.success ? 'synced' : 'error';
          await database.query(
            'UPDATE github_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
            [finalSyncStatusPublic, repositoryRecordPublic.id]
          );

          if (downloadResultPublic.success) {
            const storageInfoPublic = await githubService.getRepositoryStorage(repositoryRecordPublic.id);
            return res.status(201).json({
              success: true,
              message: 'Repository attached and synced successfully (public, no auth)',
              data: {
                repository_id: repositoryRecordPublic.id,
                repository_name: repositoryRecordPublic.repository_name,
                owner_name: repositoryRecordPublic.owner_name,
                branch_name: repositoryRecordPublic.branch_name,
                is_public: true,
                requires_auth: false,
                sync_status: finalSyncStatusPublic,
                metadata: repositoryData,
                codebase_analysis: { branch: actualBranchLocal },
                storage_info: storageInfoPublic,
                download_result: downloadResultPublic
              }
            });
          }

          // If we reach here, public clone failed: likely private → prompt OAuth now
          console.warn('Public clone attempt failed; switching to OAuth requirement');
        } catch (probeErr) {
          console.warn('Optimistic public attach failed:', probeErr.message);
        }

        // Generate an auth URL that encodes the current user AND repo context so callback can auto-attach
        const stateBase = Math.random().toString(36).substring(7);
        const userIdForAuth = userId || null;
        const encodedRepoUrl = encodeURIComponent(repository_url);
        const encodedBranchName = encodeURIComponent(branch_name || '');
        const state = `${stateBase}|uid=${userIdForAuth || ''}|repo=${encodedRepoUrl}|branch=${encodedBranchName}`;
        const rawAuthUrl = oauthService.getAuthUrl(state, userIdForAuth);

        const gatewayBase = process.env.API_GATEWAY_PUBLIC_URL || 'http://localhost:8000';
        const serviceRelative = '/api/github/auth/github';
        const serviceAuthUrl = `${gatewayBase}${serviceRelative}?redirect=1&state=${encodeURIComponent(state)}${userIdForAuth ? `&user_id=${encodeURIComponent(userIdForAuth)}` : ''}`;

        return res.status(401).json({
          success: false,
          message: 'GitHub authentication required or repository is private',
          requires_auth: true,
          auth_url: serviceAuthUrl,
          service_auth_url: rawAuthUrl,
          auth_error: false
        });
      }

      // User is authenticated, try to access the repository with auth
      try {
        const octokit = await githubService.getAuthenticatedOctokit();
        const { data: repoInfo } = await octokit.repos.get({ owner, repo });
        
        isPublicRepo = false; // This is a private repo
        repositoryData = {
          full_name: repoInfo.full_name,
          description: repoInfo.description,
          language: repoInfo.language,
          visibility: 'private',
          stargazers_count: repoInfo.stargazers_count,
          forks_count: repoInfo.forks_count,
          default_branch: repoInfo.default_branch,
          size: repoInfo.size,
          updated_at: repoInfo.updated_at
        };
        
        console.log(`✅ Private repository ${owner}/${repo} accessed with authentication`);
      } catch (authError) {
        console.log(`❌ Cannot access ${owner}/${repo} even with authentication: ${authError.message}`);
        
        return res.status(403).json({
          success: false,
          message: 'Repository not accessible - you may not have permission to access this repository'
        });
      }
    }

    // If we don't have repository data yet (private repo), fetch it with authentication
    if (!repositoryData) {
      repositoryData = await githubService.fetchRepositoryMetadata(owner, repo);
    }

    // Use the actual default branch from repository metadata if the requested branch doesn't exist
    let actualBranch = branch || branch_name || repositoryData.default_branch || 'main';
    
    // Validate that the requested branch exists, fallback to default if not
    try {
      if (branch || branch_name) {
        // Use authenticated octokit for private repos, unauthenticated for public
        let octokit;
        if (isPublicRepo) {
          octokit = new (require('@octokit/rest')).Octokit({
            userAgent: 'CodeNuk-GitIntegration/1.0.0',
          });
        } else {
          octokit = await githubService.getAuthenticatedOctokit();
        }
        
        await octokit.git.getRef({
          owner,
          repo,
          ref: `heads/${actualBranch}`
        });
      }
    } catch (error) {
      if (error.status === 404) {
        console.warn(`Branch ${actualBranch} not found, using default branch: ${repositoryData.default_branch}`);
        actualBranch = repositoryData.default_branch || 'main';
      } else {
        throw error;
      }
    }

    // Analyze the codebase
    const codebaseAnalysis = await githubService.analyzeCodebase(owner, repo, actualBranch, isPublicRepo);

    // Store everything in PostgreSQL (without template_id)
    const insertQuery = `
      INSERT INTO github_repositories (
        repository_url, repository_name, owner_name, 
        branch_name, is_public, metadata, codebase_analysis, sync_status,
        requires_auth, user_id
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
      RETURNING *
    `;

    const insertValues = [
      repository_url,
      repo,
      owner,
      actualBranch,
      isPublicRepo,
      JSON.stringify(repositoryData),
      JSON.stringify(codebaseAnalysis),
      'syncing', // Start with syncing status
      !isPublicRepo, // requires_auth is true for private repos
      userId || null
    ];

    const insertResult = await database.query(insertQuery, insertValues);
    const repositoryRecord = insertResult.rows[0];

    // Attempt to auto-create webhook on the attached repository using OAuth token (only for authenticated repos)
    let webhookResult = null;
    if (!isPublicRepo) {
      const publicBaseUrl = process.env.PUBLIC_BASE_URL || null; // e.g., your ngrok URL https://xxx.ngrok-free.app
      const callbackUrl = publicBaseUrl ? `${publicBaseUrl}/api/github/webhook` : null;
      webhookResult = await githubService.ensureRepositoryWebhook(owner, repo, callbackUrl);
    }

    // Sync with fallback: try git first, then API
    console.log(`Syncing ${isPublicRepo ? 'public' : 'private'} repository (git first, API fallback)...`);
    const downloadResult = await githubService.syncRepositoryWithFallback(
      owner, repo, actualBranch, repositoryRecord.id, isPublicRepo
    );

    // Update sync status based on download result
    const finalSyncStatus = downloadResult.success ? 'synced' : 'error';
    await database.query(
      'UPDATE github_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
      [finalSyncStatus, repositoryRecord.id]
    );

    if (!downloadResult.success) {
      console.warn('Repository download failed:', downloadResult.error);
    } else {
      console.log(`✅ Repository ${owner}/${repo} synced successfully using ${downloadResult.method} method`);
    }

    // Get storage information
    const storageInfo = await githubService.getRepositoryStorage(repositoryRecord.id);

    res.status(201).json({
      success: true,
      message: `Repository attached and ${downloadResult.success ? 'synced' : 'partially synced'} successfully`,
      data: {
        repository_id: repositoryRecord.id,
        repository_name: repositoryRecord.repository_name,
        owner_name: repositoryRecord.owner_name,
        branch_name: repositoryRecord.branch_name,
        is_public: isPublicRepo,
        requires_auth: !isPublicRepo,
        sync_status: finalSyncStatus,
        metadata: repositoryData,
        codebase_analysis: codebaseAnalysis,
        storage_info: storageInfo,
        download_result: downloadResult,
        webhook_result: webhookResult,
        authentication_info: {
          is_public: isPublicRepo,
          authenticated: !isPublicRepo,
          github_username: null
        }
      }
    });

  } catch (error) {
    console.error('Error attaching repository:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to attach repository'
    });
  }
});
// Get repository diff between two SHAs (unified patch)
router.get('/repository/:id/diff', async (req, res) => {
  try {
    const { id } = req.params;
    const { from, to, path: dirPath } = req.query;

    const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
    const repoResult = await database.query(repoQuery, [id]);
    if (repoResult.rows.length === 0) {
      return res.status(404).json({ success: false, message: 'Repository not found' });
    }
    const record = repoResult.rows[0];
    const { owner, repo } = githubService.parseGitHubUrl(record.repository_url);
    // Always use stored branch_name to avoid mismatches like master/main
    const targetBranch = record.branch_name || 'main';
    const patch = await githubService.getRepositoryDiff(owner, repo, targetBranch, from || record.last_synced_commit_sha, to || 'HEAD');
    res.json({ success: true, data: { patch, from: from || record.last_synced_commit_sha, to: to || 'HEAD' } });
  } catch (error) {
    console.error('Error getting diff:', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to get diff' });
  }
});

// Get list of changed files since a SHA
router.get('/repository/:id/changes', async (req, res) => {
  try {
    const { id } = req.params;
    const { since } = req.query;

    const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
    const repoResult = await database.query(repoQuery, [id]);
    if (repoResult.rows.length === 0) {
      return res.status(404).json({ success: false, message: 'Repository not found' });
    }
    const record = repoResult.rows[0];
    const { owner, repo, branch } = githubService.parseGitHubUrl(record.repository_url);

    const sinceSha = since || record.last_synced_commit_sha;
    if (!sinceSha) {
      return res.status(400).json({ success: false, message: 'since SHA is required or must be available as last_synced_commit_sha' });
    }

    const changes = await githubService.getRepositoryChangesSince(owner, repo, branch || record.branch_name, sinceSha);
    res.json({ success: true, data: { since: sinceSha, changes } });
  } catch (error) {
    console.error('Error getting changes:', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to get changes' });
  }
});

// Get repository information for a template
router.get('/template/:id/repository', async (req, res) => {
  try {
    const { id } = req.params;
    
    const query = `
      SELECT gr.*, rs.local_path, rs.storage_status, rs.total_files_count, 
             rs.total_directories_count, rs.total_size_bytes, rs.download_completed_at
      FROM github_repositories gr
      LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
      WHERE gr.template_id = $1 
      ORDER BY gr.created_at DESC 
      LIMIT 1
    `;
    
    const result = await database.query(query, [id]);
    
    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'No repository found for this template'
      });
    }
    
    const repository = result.rows[0];

    const parseMaybe = (v) => {
      if (v == null) return {};
      if (typeof v === 'string') {
        try { return JSON.parse(v); } catch { return {}; }
      }
      return v; // already an object from jsonb
    };
    
    res.json({
      success: true,
      data: {
        ...repository,
        metadata: parseMaybe(repository.metadata),
        codebase_analysis: parseMaybe(repository.codebase_analysis)
      }
    });
    
  } catch (error) {
    console.error('Error fetching repository:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch repository'
    });
  }
});

// Get repository file structure
router.get('/repository/:id/structure', async (req, res) => {
  try {
    const { id } = req.params;
    const { path: directoryPath } = req.query;
    
    // Get repository info
    const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
    const repoResult = await database.query(repoQuery, [id]);
    
    if (repoResult.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Repository not found'
      });
    }

    const structure = await fileStorageService.getRepositoryStructure(id, directoryPath);
    
    res.json({
      success: true,
      data: {
        repository_id: id,
        directory_path: directoryPath || '',
        structure: structure
      }
    });
    
  } catch (error) {
    console.error('Error fetching repository structure:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch repository structure'
    });
  }
});

// Get files in a directory
router.get('/repository/:id/files', async (req, res) => {
  try {
    const { id } = req.params;
    const { directory_path = '' } = req.query;
    
    // Get repository info
    const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
    const repoResult = await database.query(repoQuery, [id]);
    
    if (repoResult.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Repository not found'
      });
    }

    const files = await fileStorageService.getDirectoryFiles(id, directory_path);
    
    res.json({
      success: true,
      data: {
        repository_id: id,
        directory_path: directory_path,
        files: files
      }
    });
    
  } catch (error) {
    console.error('Error fetching directory files:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch directory files'
    });
  }
});

// Get file content
router.get('/repository/:id/file-content', async (req, res) => {
  try {
    const { id } = req.params;
    const { file_path } = req.query;
    
    if (!file_path) {
      return res.status(400).json({
        success: false,
        message: 'File path is required'
      });
    }

    const query = `
      SELECT rf.*, rfc.content_text, rfc.content_preview, rfc.language_detected, 
             rfc.line_count, rfc.char_count
      FROM repository_files rf
      LEFT JOIN repository_file_contents rfc ON rf.id = rfc.file_id
      WHERE rf.repository_id = $1 AND rf.relative_path = $2
    `;
    
    const result = await database.query(query, [id, file_path]);
    
    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'File not found'
      });
    }
    
    const file = result.rows[0];
    
    res.json({
      success: true,
      data: {
        file_info: {
          id: file.id,
          filename: file.filename,
          file_extension: file.file_extension,
          relative_path: file.relative_path,
          file_size_bytes: file.file_size_bytes,
          mime_type: file.mime_type,
          is_binary: file.is_binary,
          language_detected: file.language_detected,
          line_count: file.line_count,
          char_count: file.char_count
        },
        content: file.is_binary ? null : file.content_text,
        preview: file.content_preview
      }
    });
    
  } catch (error) {
    console.error('Error fetching file content:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch file content'
    });
  }
});

// Search repository files
router.get('/repository/:id/search', async (req, res) => {
  try {
    const { id } = req.params;
    const { q: query } = req.query;
    
    if (!query) {
      return res.status(400).json({
        success: false,
        message: 'Search query is required'
      });
    }

    const results = await fileStorageService.searchFileContent(id, query);
    
    res.json({
      success: true,
      data: {
        repository_id: id,
        search_query: query,
        results: results,
        total_results: results.length
      }
    });
    
  } catch (error) {
    console.error('Error searching repository:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to search repository'
    });
  }
});

// List all repositories for a template
router.get('/template/:id/repositories', async (req, res) => {
  try {
    const { id } = req.params;
    
    const query = `
      SELECT gr.*, rs.local_path, rs.storage_status, rs.total_files_count, 
             rs.total_directories_count, rs.total_size_bytes, rs.download_completed_at
      FROM github_repositories gr
      LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
      WHERE gr.template_id = $1 
      ORDER BY gr.created_at DESC
    `;
    
    const result = await database.query(query, [id]);
    
    const repositories = result.rows.map(repo => ({
      ...repo,
      metadata: JSON.parse(repo.metadata || '{}'),
      codebase_analysis: JSON.parse(repo.codebase_analysis || '{}')
    }));
    
    res.json({
      success: true,
      data: repositories
    });
    
  } catch (error) {
    console.error('Error fetching repositories:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch repositories'
    });
  }
});

// Download repository files (legacy endpoint for backward compatibility)
router.post('/download', async (req, res) => {
  try {
    const { repository_url, branch_name } = req.body;
    
    if (!repository_url) {
      return res.status(400).json({
        success: false,
        message: 'Repository URL is required'
      });
    }
    
    const { owner, repo, branch } = githubService.parseGitHubUrl(repository_url);
    const targetBranch = branch || branch_name || 'main';
    
    const result = await githubService.downloadRepository(owner, repo, targetBranch);
    
    if (result.success) {
      res.json({
        success: true,
        message: 'Repository downloaded successfully',
        data: result
      });
    } else {
      res.status(500).json({
        success: false,
        message: 'Failed to download repository',
        error: result.error
      });
    }
    
  } catch (error) {
    console.error('Error downloading repository:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to download repository'
    });
  }
});

// Re-sync repository (re-download and update database)
router.post('/repository/:id/sync', async (req, res) => {
  try {
    const { id } = req.params;
    
    // Get repository info
    const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
    const repoResult = await database.query(repoQuery, [id]);
    
    if (repoResult.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Repository not found'
      });
    }
    
    const repository = repoResult.rows[0];
    const { owner, repo, branch } = githubService.parseGitHubUrl(repository.repository_url);
    
    // Clean up existing storage
    await githubService.cleanupRepositoryStorage(id);
    
    // Re-sync with fallback (git first, API fallback)
    const downloadResult = await githubService.syncRepositoryWithFallback(
      owner, repo, branch || repository.branch_name, id
    );
    
    // Update sync status
    await database.query(
      'UPDATE github_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
      [downloadResult.success ? 'synced' : 'error', id]
    );
    
    res.json({
      success: downloadResult.success,
      message: downloadResult.success ? 'Repository synced successfully' : 'Failed to sync repository',
      data: downloadResult
    });
    
  } catch (error) {
    console.error('Error syncing repository:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to sync repository'
    });
  }
});

// Remove repository from template
router.delete('/repository/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    // Get repository info before deletion
    const getQuery = 'SELECT * FROM github_repositories WHERE id = $1';
    const getResult = await database.query(getQuery, [id]);
    
    if (getResult.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Repository not found'
      });
    }
    
    const repository = getResult.rows[0];
    
    // Clean up file storage
    await githubService.cleanupRepositoryStorage(id);
    
    // Delete feature mappings first
    await database.query(
      'DELETE FROM feature_codebase_mappings WHERE repository_id = $1',
      [id]
    );
    
    // Delete repository record
    await database.query(
      'DELETE FROM github_repositories WHERE id = $1',
      [id]
    );
    
    res.json({
      success: true,
      message: 'Repository removed successfully',
      data: {
        removed_repository: repository.repository_name,
        template_id: repository.template_id
      }
    });
    
  } catch (error) {
    console.error('Error removing repository:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to remove repository'
    });
  }
});

module.exports = router;