// routes/vcs.routes.js
const express = require('express');
const router = express.Router({ mergeParams: true });
const providerRegistry = require('../services/provider-registry');
const database = require('../config/database');
const FileStorageService = require('../services/file-storage.service');

const fileStorageService = new FileStorageService();
const GitLabOAuthService = require('../services/gitlab-oauth');
const BitbucketOAuthService = require('../services/bitbucket-oauth');
const GiteaOAuthService = require('../services/gitea-oauth');
const VcsWebhookService = require('../services/vcs-webhook.service');

const vcsWebhookService = new VcsWebhookService();

function getProvider(req) {
  const providerKey = (req.params.provider || '').toLowerCase();
  return providerRegistry.resolve(providerKey);
}

function getOAuthService(providerKey) {
  if (providerKey === 'gitlab') return new GitLabOAuthService();
  if (providerKey === 'bitbucket') return new BitbucketOAuthService();
  if (providerKey === 'gitea') return new GiteaOAuthService();
  return null;
}

function extractEventType(providerKey, payload) {
  switch (providerKey) {
    case 'gitlab':
      return payload.object_kind || (payload.ref ? 'push' : 'unknown');
    case 'bitbucket':
      return (payload.push && 'push') || (payload.pullrequest && 'pull_request') || 'unknown';
    case 'gitea':
      return payload.action || (payload.ref ? 'push' : 'unknown');
    default:
      return 'unknown';
  }
}

// Attach repository (provider-agnostic)
router.post('/:provider/attach-repository', async (req, res) => {
  try {
    const provider = getProvider(req);
    const { template_id, 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));

    if (!template_id || !repository_url) {
      return res.status(400).json({ success: false, message: 'Template ID and repository URL are required' });
    }

    const templateResult = await database.query('SELECT * FROM templates WHERE id = $1 AND is_active = true', [template_id]);
    if (templateResult.rows.length === 0) {
      return res.status(404).json({ success: false, message: 'Template not found' });
    }

    const { owner, repo, branch } = provider.parseRepoUrl(repository_url);
    const accessCheck = await provider.checkRepositoryAccess(owner, repo);

    if (!accessCheck.hasAccess) {
      if (accessCheck.requiresAuth) {
        // Check if we have OAuth token for this provider
        const providerKey = (req.params.provider || '').toLowerCase();
        const oauthService = getOAuthService(providerKey);
        if (oauthService) {
          const tokenRecord = await oauthService.getToken();
          if (!tokenRecord) {
            return res.status(401).json({
              success: false,
              message: `${providerKey.charAt(0).toUpperCase() + providerKey.slice(1)} authentication required for this repository`,
              requires_auth: true,
              auth_url: `/api/vcs/${providerKey}/auth/start`
            });
          }
        }
      }
      
      return res.status(404).json({ success: false, message: accessCheck.error || 'Repository not accessible' });
    }

    const repositoryData = await provider.fetchRepositoryMetadata(owner, repo);
    let actualBranch = branch || branch_name || repositoryData.default_branch || 'main';

    try {
      // No-op for non-GitHub providers if not supported; adapters can throw if needed
    } catch (_) {}

    // Preliminary analysis (may be refined after full sync)
    let codebaseAnalysis = await provider.analyzeCodebase(owner, repo, actualBranch);

    // For backward-compatibility, insert into all_repositories for now
    const insertQuery = `
      INSERT INTO all_repositories (
        template_id, 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, $11)
      RETURNING *
    `;
    const insertValues = [
      template_id,
      repository_url,
      repo,
      owner,
      actualBranch,
      repositoryData.visibility === 'public',
      JSON.stringify(repositoryData),
      JSON.stringify(codebaseAnalysis),
      'synced',
      accessCheck.requiresAuth,
      userId || null
    ];
    const insertResult = await database.query(insertQuery, insertValues);
    const repositoryRecord = insertResult.rows[0];

    const publicBaseUrl = process.env.PUBLIC_BASE_URL || null;
    const callbackUrl = publicBaseUrl ? `${publicBaseUrl}/api/vcs/${req.params.provider}/webhook` : null;
    try { await provider.ensureRepositoryWebhook(owner, repo, callbackUrl); } catch (_) {}

    const downloadResult = await provider.syncRepositoryWithFallback(owner, repo, actualBranch, repositoryRecord.id);

    // Recompute analysis from indexed storage for accurate counts
    try {
      const aggQuery = `
        SELECT 
          COALESCE(SUM(rf.total_size_bytes), 0) AS total_size,
          COALESCE(COUNT(rf.id), 0) AS total_files,
          COALESCE((SELECT COUNT(1) FROM repository_directories rd WHERE rd.storage_id = rs.id), 0) AS total_directories
        FROM repository_storage rs
        LEFT JOIN repository_files rf ON rs.id = rf.storage_id
        WHERE rs.repository_id = $1
        GROUP BY rs.id
        LIMIT 1
      `;
      const aggRes = await database.query(aggQuery, [repositoryRecord.id]);
      if (aggRes.rows.length > 0) {
        const agg = aggRes.rows[0];
        codebaseAnalysis = {
          total_files: Number(agg.total_files) || 0,
          total_size: Number(agg.total_size) || 0,
          directories: [],
          branch: actualBranch
        };
        // Persist refined analysis
        await database.query('UPDATE all_repositories SET codebase_analysis = $1, updated_at = NOW() WHERE id = $2', [JSON.stringify(codebaseAnalysis), repositoryRecord.id]);
      }
    } catch (_) {}

    // Create empty feature mappings like existing flow
    const featureResult = await database.query('SELECT id FROM template_features WHERE template_id = $1', [template_id]);
    if (featureResult.rows.length > 0) {
      const mappingValues = [];
      const params = [];
      let i = 1;
      for (const feature of featureResult.rows) {
        mappingValues.push(`(uuid_generate_v4(), $${i++}, $${i++}, $${i++}, $${i++})`);
        params.push(feature.id, repositoryRecord.id, '[]', '{}');
      }
     
    }

    const storageInfo = await (async () => {
      const q = `
        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 r = await database.query(q, [repositoryRecord.id]);
      return r.rows[0] || null;
    })();

    res.status(201).json({
      success: true,
      message: 'Repository attached successfully',
      data: {
        repository_id: repositoryRecord.id,
        template_id: repositoryRecord.template_id,
        repository_name: repositoryRecord.repository_name,
        owner_name: repositoryRecord.owner_name,
        branch_name: repositoryRecord.branch_name,
        is_public: repositoryRecord.is_public,
        requires_auth: repositoryRecord.requires_auth,
        metadata: repositoryData,
        codebase_analysis: codebaseAnalysis,
        storage_info: storageInfo,
        download_result: downloadResult
      }
    });
  } catch (error) {
    console.error('Error attaching repository (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to attach repository' });
  }
});

// Generic webhook endpoint with provider-specific verification and push handling
router.post('/:provider/webhook', async (req, res) => {
  try {
    const providerKey = (req.params.provider || '').toLowerCase();
    const payload = req.body || {};

    if (providerKey === 'github') {
      return res.status(400).json({ success: false, message: 'Use /api/github/webhook for GitHub' });
    }

    // Signature verification
    const rawBody = req.rawBody ? req.rawBody : Buffer.from(JSON.stringify(payload));
    const verifySignature = () => {
      try {
        if (providerKey === 'gitlab') {
          const token = req.headers['x-gitlab-token'];
          const secret = process.env.GITLAB_WEBHOOK_SECRET;
          if (!secret) return true; // if not set, skip
          return token && token === secret;
        }
        if (providerKey === 'gitea') {
          const crypto = require('crypto');
          const providedHeader = req.headers['x-gitea-signature'] || req.headers['x-gogs-signature'] || req.headers['x-hub-signature-256'];
          const secret = process.env.GITEA_WEBHOOK_SECRET;
          if (!secret) return true;
          if (!providedHeader) return false;
          let provided = String(providedHeader);
          if (provided.startsWith('sha256=')) provided = provided.slice('sha256='.length);
          const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  
          try {
            return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(provided, 'hex'));
          } catch (_) {
            return false;
          }
        }
        if (providerKey === 'bitbucket') {
          // Bitbucket Cloud webhooks typically have no shared secret by default
          return true;
        }
        return false;
      } catch (_) {
        return false;
      }
    };

    if (!verifySignature()) {
      return res.status(401).json({ success: false, message: 'Invalid webhook signature' });
    }

    if (providerKey === 'bitbucket') {
      console.log('🔔 Bitbucket webhook received:', {
        eventKey: req.headers['x-event-key'],
        requestId: req.headers['x-request-id'],
        userAgent: req.headers['user-agent'],
        payloadSize: rawBody?.length || 0
      });
    }
    // Process webhook event using comprehensive service
    const eventType = extractEventType(providerKey, payload);
    await vcsWebhookService.processWebhookEvent(providerKey, eventType, payload);

    return res.status(200).json({ success: true, message: 'Webhook processed', provider: providerKey, event_type: eventType });
  } catch (error) {
    console.error('Error in VCS webhook:', error);
    res.status(500).json({ success: false, message: 'Failed to process webhook' });
  }
});

module.exports = router;
// Additional provider-agnostic routes mirroring GitHub endpoints

// Get repository diff between two SHAs (unified patch)
router.get('/:provider/repository/:id/diff', async (req, res) => {
  try {
    const provider = getProvider(req);
    const { id } = req.params;
    const { from, to } = req.query;
    const repoQuery = 'SELECT * FROM all_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 } = provider.parseRepoUrl(record.repository_url);
    // Use stored branch_name to avoid master/main mismatch
    const targetBranch = record.branch_name || 'main';
    const patch = await provider.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 (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to get diff' });
  }
});

// Get list of changed files since a SHA
router.get('/:provider/repository/:id/changes', async (req, res) => {
  try {
    const provider = getProvider(req);
    const { id } = req.params;
    const { since } = req.query;
    const repoQuery = 'SELECT * FROM all_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 } = provider.parseRepoUrl(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 provider.getRepositoryChangesSince(owner, repo, record.branch_name, sinceSha);
    res.json({ success: true, data: { since: sinceSha, changes } });
  } catch (error) {
    console.error('Error getting changes (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to get changes' });
  }
});

// Get repository information for a template (latest)
router.get('/:provider/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 all_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;
    };
    res.json({ success: true, data: { ...repository, metadata: parseMaybe(repository.metadata), codebase_analysis: parseMaybe(repository.codebase_analysis) } });
  } catch (error) {
    console.error('Error fetching repository (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to fetch repository' });
  }
});

// Get repository file structure
router.get('/:provider/repository/:id/structure', async (req, res) => {
  try {
    const { id } = req.params;
    const { path: directoryPath } = req.query;
    const repoQuery = 'SELECT * FROM all_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 } });
  } catch (error) {
    console.error('Error fetching repository structure (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to fetch repository structure' });
  }
});

// Get files in a directory
router.get('/:provider/repository/:id/files', async (req, res) => {
  try {
    const { id } = req.params;
    const { directory_path = '' } = req.query;
    const repoQuery = 'SELECT * FROM all_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, files } });
  } catch (error) {
    console.error('Error fetching directory files (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to fetch directory files' });
  }
});

// Get file content
router.get('/:provider/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.*t
      FROM repository_files rf
      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.total_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 (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to fetch file content' });
  }
});

// List all repositories for a template
router.get('/:provider/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 all_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 (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to fetch repositories' });
  }
});

// Re-sync repository (git-based)
router.post('/:provider/repository/:id/sync', async (req, res) => {
  try {
    const provider = getProvider(req);
    const { id } = req.params;
    const repoQuery = 'SELECT * FROM all_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 } = provider.parseRepoUrl(repository.repository_url);
    await provider.cleanupRepositoryStorage(id);
    const downloadResult = await provider.syncRepositoryWithFallback(owner, repo, branch || repository.branch_name, id);
    await database.query('UPDATE all_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 (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to sync repository' });
  }
});

// Remove repository
router.delete('/:provider/repository/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const getResult = await database.query('SELECT * FROM all_repositories WHERE id = $1', [id]);
    if (getResult.rows.length === 0) {
      return res.status(404).json({ success: false, message: 'Repository not found' });
    }
    const repository = getResult.rows[0];
    await fileStorageService.cleanupRepositoryStorage(id);
    await database.query('DELETE FROM all_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 (vcs):', error);
    res.status(500).json({ success: false, message: error.message || 'Failed to remove repository' });
  }
});

// OAuth placeholders (start/callback) per provider for future implementation
router.get('/:provider/auth/start', async (req, res) => {
  try {
    const providerKey = (req.params.provider || '').toLowerCase();
    const oauth = getOAuthService(providerKey);
    if (!oauth) return res.status(400).json({ success: false, message: 'Unsupported provider or OAuth not available' });
    const state = req.query.state || Math.random().toString(36).slice(2);
    const url = oauth.getAuthUrl(state);
    res.json({ success: true, auth_url: url, provider: providerKey, state });
  } catch (e) {
    res.status(500).json({ success: false, message: e.message || 'Failed to start OAuth' });
  }
});

router.get('/:provider/auth/callback', (req, res) => {
  (async () => {
    try {
      const providerKey = (req.params.provider || '').toLowerCase();
      const code = req.query.code;
      const error = req.query.error;
      const errorDescription = req.query.error_description;
      console.log(`🔄 [VCS OAUTH] Callback received for ${providerKey}:`, { 
        hasCode: !!code, 
        hasError: !!error, 
        code: code?.substring(0, 10) + '...',
        error,
        errorDescription
      });
      const oauth = getOAuthService(providerKey);
      if (!oauth) return res.status(400).json({ success: false, message: 'Unsupported provider or OAuth not available' });
      if (!code) {
        // Surface upstream provider error details if present
        if (error || errorDescription) {
          console.error(`❌ [VCS OAUTH] Provider error for ${providerKey}:`, { error, errorDescription });
          return res.status(400).json({ 
            success: false, 
            message: 'OAuth error from provider', 
            provider: providerKey, 
            error: error || 'unknown_error', 
            error_description: errorDescription || null, 
            query: req.query 
          });
        }
        return res.status(400).json({ success: false, message: 'Missing authorization code' });
      }
      const accessToken = await oauth.exchangeCodeForToken(code);
      const user = await oauth.getUserInfo(accessToken);
      const userId =
        req.query.user_id ||
        (req.body && req.body.user_id) ||
        req.headers['x-user-id'] ||
        (req.cookies && (req.cookies.user_id || req.cookies.uid)) ||
        (req.session && req.session.user && (req.session.user.id || req.session.user.userId)) ||
        (req.user && (req.user.id || req.user.userId));
      if (providerKey === 'github' && !userId) {
        return res.status(400).json({ success: false, message: 'user_id is required to complete GitHub authentication' });
      }
      console.log('[VCS OAuth] callback provider=%s resolved user_id = %s', providerKey, userId || null);
      const tokenRecord = await oauth.storeToken(accessToken, user, userId || null);
      res.json({ success: true, provider: providerKey, user, token: { id: tokenRecord.id || null } });
    } catch (e) {
    
      console.error(`❌ [VCS OAUTH] Callback error for ${req.params.provider}:`, e);
      
      // Provide more specific error messages
      let errorMessage = e.message || 'OAuth callback failed';
      let statusCode = 500;
      
      if (e.message.includes('not configured')) {
        statusCode = 500;
        errorMessage = `OAuth configuration error: ${e.message}`;
      } else if (e.message.includes('timeout')) {
        statusCode = 504;
        errorMessage = `OAuth timeout: ${e.message}`;
      } else if (e.message.includes('network error') || e.message.includes('Cannot connect')) {
        statusCode = 502;
        errorMessage = `Network error: ${e.message}`;
      } else if (e.message.includes('HTTP error')) {
        statusCode = 502;
        errorMessage = `OAuth provider error: ${e.message}`;
      }
      
      res.status(statusCode).json({ 
        success: false, 
        message: errorMessage,
        provider: req.params.provider,
        error: e.message,
        details: process.env.NODE_ENV === 'development' ? e.stack : undefined
      });
    }
  })();
});

// UI View endpoint for all providers - GitHub-like interface
router.get('/:provider/repository/:id/ui-view', async (req, res) => {
  try {
    const { provider, id } = req.params;
    const {
      view_type = 'tree',  // tree, commit, blob
      commit_sha = null,
      path = '',
      file_path = null,
      base_commit = null,
      target_commit = null
    } = req.query;

    // Validate provider
    const providerKey = provider.toLowerCase();
    if (!['github', 'gitlab', 'bitbucket', 'gitea'].includes(providerKey)) {
      return res.status(400).json({
        success: false,
        message: 'Invalid provider. Must be: github, gitlab, bitbucket, or gitea'
      });
    }

    // Get repository info - all repositories are stored in all_repositories table
    let repository;
    
    const repoQuery = `
      SELECT gr.*, rs.storage_status, rs.local_path, rs.total_files_count
      FROM all_repositories gr
      LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
      WHERE gr.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'
      });
    }

    repository = repoResult.rows[0];

    if (repository.storage_status !== 'completed') {
      return res.status(400).json({
        success: false,
        message: 'Repository not fully synced. Please wait for sync to complete.',
        sync_status: repository.storage_status
      });
    }

    // Handle different view types
    let uiData;
    
    switch (view_type) {
      case 'commit':
        uiData = await handleCommitView(id, {
          commit_sha,
          base_commit,
          target_commit,
          file_path
        });
        break;
        
      case 'tree':
        uiData = await handleTreeView(id, {
          commit_sha,
          path
        });
        break;
        
      case 'blob':
        uiData = await handleBlobView(id, {
          file_path,
          commit_sha
        });
        break;
        
      default:
        return res.status(400).json({
          success: false,
          message: 'Invalid view_type. Must be: tree, commit, or blob'
        });
    }

    res.json({
      success: true,
      data: {
        repository_info: {
          id: repository.id,
          name: repository.repository_name,
          owner: repository.owner_name,
          branch: repository.branch_name,
          last_synced: repository.last_synced_at,
          repository_url: repository.repository_url,
          provider: providerKey
        },        ui_data: uiData
      }
    });

  } catch (error) {
    console.error(`Error fetching UI view for ${req.params.provider}:`, error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch UI view'
    });
  }
});

// Helper function for commit view
async function handleCommitView(repositoryId, options) {
  const { commit_sha, base_commit, target_commit, file_path } = options;
  
  if (!base_commit || !target_commit) {
    throw new Error('base_commit and target_commit are required for commit view');
  }

  // Get file tree with changes
  const fileTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
    includeChanges: true,
    baseCommit: base_commit,
    targetCommit: target_commit
  });

  // Get diff summary
  const summary = await fileStorageService.getDiffSummary(repositoryId, base_commit, target_commit);

  // Get selected file diff (default to first changed file)
  let selectedFile = null;
  let diffData = { changes: [] };
  
  if (file_path) {
    selectedFile = findFileInTree(fileTree, file_path);
    if (selectedFile) {
      diffData = await fileStorageService.getFileDiff(repositoryId, file_path, base_commit, target_commit);
    }
  } else {
    // Find first changed file
    selectedFile = findFirstChangedFile(fileTree);
    if (selectedFile) {
      diffData = await fileStorageService.getFileDiff(repositoryId, selectedFile.path, base_commit, target_commit);
    }
  }


  return {
    left_panel: {
      file_tree: minimalTree,
      summary: summary
    },
    right_panel: {
      file_info: selectedFile,
      diff_data: diffData
    }
  };
}

// Helper function for tree view
async function handleTreeView(repositoryId, options) {
  const { commit_sha, path } = options;

  // Get file tree without changes
  const fileTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
    includeChanges: false
  });

  // Filter by path if specified
  let filteredTree = fileTree;
  if (path) {
    filteredTree = filterTreeByPath(fileTree, path);
  }

  return {
    left_panel: {
      file_tree: filteredTree,
      summary: {
        total_files_changed: 0,
        total_additions: 0,
        total_deletions: 0
      }
    },
    right_panel: {
      file_info: null,
      diff_data: { changes: [] }
    }
  };
}

// Helper function for blob view
async function handleBlobView(repositoryId, options) {
  const { file_path, commit_sha } = options;

  if (!file_path) {
    throw new Error('file_path is required for blob view');
  }

  // Get minimal file tree (only path to the file)
  const minimalTree = await getMinimalFileTree(repositoryId, file_path);

  // If minimal tree is null, try to find file directly
  let selectedFile;
  if (minimalTree) {
    selectedFile = findFileInTree(minimalTree, file_path);
  } else {
    // Fallback: get full tree and find file
    const fullTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
      includeChanges: false
    });
    selectedFile = findFileInTree(fullTree, file_path);
  }
  
  if (!selectedFile) {
    throw new Error('File not found: ' + file_path);
  }

  // Get absolute path from database for file reading
  const fileRecord = await fileStorageService.getFileByPath(repositoryId, file_path);
  if (!fileRecord) {
    throw new Error('File record not found in database: ' + file_path);
  }

  // Read file content using absolute path from database
  const content = await fileStorageService.readFileContentForAI(fileRecord.absolute_path, fileRecord.is_binary);

  // Remove absolute_path from file_info for security
  const { absolute_path, ...fileInfoWithoutAbsolutePath } = selectedFile;
  
  return {
    left_panel: {
      file_tree: minimalTree,
      summary: {
        total_files_changed: 0,
        total_additions: 0,
        total_deletions: 0
      }
    },
    right_panel: {
      file_info: {
        ...fileInfoWithoutAbsolutePath,
        content: content
      },
      diff_data: { changes: [] }
    }
  };
}

// Helper function to find file in tree
function findFileInTree(tree, targetPath) {
  
  if (tree.type === 'file') {
    
    // Exact match
    if (tree.path === targetPath) {
      return tree;
    }
    
    // Check if the target path ends with the file path (for relative paths)
    if (tree.path.endsWith(targetPath)) {
      return tree;
    }
    
    // Check if the file name matches (for simple file names)
    const fileName = tree.name;
    const targetFileName = targetPath.split('/').pop();
    if (fileName === targetFileName) {
      return tree;
    }
  }
  
  if (tree.children) {
    for (const child of tree.children) {
      const found = findFileInTree(child, targetPath);
      if (found) return found;
    }
  }
  
  return null;
}

// Helper function to find first changed file
function findFirstChangedFile(tree) {
  if (tree.type === 'file' && tree.change_status) {
    return tree;
  }
  
  if (tree.children) {
    for (const child of tree.children) {
      const found = findFirstChangedFile(child);
      if (found) return found;
    }
  }
  
  return null;
}

// Helper function to filter tree by path
function filterTreeByPath(tree, targetPath) {
  // If targetPath is empty or matches root, return the whole tree
  if (!targetPath || targetPath === '' || targetPath === '/') {
    return tree;
  }
  
  // Clean the target path
  const cleanPath = targetPath.replace(/^\/+|\/+$/g, ''); // Remove leading/trailing slashes
  
  // Find the directory that matches the target path
  function findDirectoryByPath(node, path) {
    if (node.path === path) {
      return node;
    }
    
    if (node.children) {
      for (const child of node.children) {
        if (child.type === 'directory') {
          const found = findDirectoryByPath(child, path);
          if (found) return found
        }
      }
    }
    
    return null;
  }
  
  const foundDir = findDirectoryByPath(tree, cleanPath);
  
  if (foundDir) {
    return foundDir;
  }
  
  // If exact match not found, try to find a directory that contains the path
  function findDirectoryByContains(node, path) {
    if (node.path && node.path.includes(path)) {
      return node;
    }
    
    if (node.children) {
      for (const child of node.children) {
        if (child.type === 'directory') {
          const found = findDirectoryByContains(child, path);
          if (found) return found;
        }
      }
    }
    
    return null;
  }
  
  const foundByContains = findDirectoryByContains(tree, cleanPath);
  if (foundByContains) {
    return foundByContains;
  }
  
  return tree;
}

// UI View endpoint for all providers - GitHub-like interface
router.get('/:provider/repository/:id/ui-view', async (req, res) => {
  try {
    const { provider, id } = req.params;
    const {
      view_type = 'tree',  // tree, commit, blob
      commit_sha = null,
      path = '',
      file_path = null,
      base_commit = null,
      target_commit = null
    } = req.query;

    // Validate provider
    const providerKey = provider.toLowerCase();
    if (!['github', 'gitlab', 'bitbucket', 'gitea'].includes(providerKey)) {
      return res.status(400).json({
        success: false,
        message: 'Invalid provider. Must be: github, gitlab, bitbucket, or gitea'
      });
    }

    // Get repository info - all repositories are stored in all_repositories table
    let repository;
  
    const repoQuery = `
      SELECT gr.*, rs.storage_status, rs.local_path, rs.total_files_count
      FROM all_repositories gr
      LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
      WHERE gr.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'
      });
    }

    repository = repoResult.rows[0];

    if (repository.storage_status !== 'completed') {
      return res.status(400).json({
        success: false,
        message: 'Repository not fully synced. Please wait for sync to complete.',
        sync_status: repository.storage_status
      });
    }

    // Handle different view types
    let uiData;
    
    switch (view_type) {
      case 'commit':
        uiData = await handleCommitView(id, {
          commit_sha,
          base_commit,
          target_commit,
          file_path
        });
        break;
        
      case 'tree':
        uiData = await handleTreeView(id, {
          commit_sha,
          path
        });
        break;
        
      case 'blob':
        uiData = await handleBlobView(id, {
          file_path,
          commit_sha
        });
        break;
        
      default:
        return res.status(400).json({
          success: false,
          message: 'Invalid view_type. Must be: tree, commit, or blob'
        });
    }

    res.json({
      success: true,
      data: {
        repository_info: {
          id: repository.id,
          name: repository.repository_name,
          owner: repository.owner_name,
          branch: repository.branch_name,
          last_synced: repository.last_synced_at,
          repository_url: repository.repository_url,
          provider: providerKey
        },
        ui_data: uiData
      }
    });

  } catch (error) {
    console.error(`Error fetching UI view for ${req.params.provider}:`, error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to fetch UI view'
    });
  }

});

// Helper function for commit vie
async function handleCommitView(repositoryId, options) {
  const { commit_sha, base_commit, target_commit, file_path } = options;
  
  if (!base_commit || !target_commit) {
    throw new Error('base_commit and target_commit are required for commit view');
  }

  // Get file tree with changes
  const fileTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
    includeChanges: true,
    baseCommit: base_commit,
    targetCommit: target_commit
  });

  // Get diff summary
  const summary = await fileStorageService.getDiffSummary(repositoryId, base_commit, target_commit);

  // Get selected file diff (default to first changed file)
  let selectedFile = null;
  let diffData = { changes: [] };
  
  if (file_path) {
    selectedFile = findFileInTree(fileTree, file_path);
    if (selectedFile) {
      diffData = await fileStorageService.getFileDiff(repositoryId, file_path, base_commit, target_commit);
    }
  } else {
    // Find first changed file
    selectedFile = findFirstChangedFile(fileTree);
    if (selectedFile) {
      diffData = await fileStorageService.getFileDiff(repositoryId, selectedFile.path, base_commit, target_commit);
    }
  }

  return {
    left_panel: {
      file_tree: minimalTree,
      summary: summary
    },
    right_panel: {
      file_info: selectedFile,
      diff_data: diffData
    }
  };
}

// Helper function for tree view
async function handleTreeView(repositoryId, options) {
  const { commit_sha, path } = options;

  // Get file tree without changes
  const fileTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
    includeChanges: false
  });

  // Filter by path if specified
  let filteredTree = fileTree;
  if (path) {
    filteredTree = filterTreeByPath(fileTree, path);
  }

  return {
    left_panel: {
      file_tree: filteredTree,
      summary: {
        total_files_changed: 0,
        total_additions: 0,
        total_deletions: 0
      }
    },
    right_panel: {
      file_info: null,
      diff_data: { changes: [] }
    }
  };
}

// Helper function for blob view
async function handleBlobView(repositoryId, options) {
  const { file_path, commit_sha } = options;

  if (!file_path) {
    throw new Error('file_path is required for blob view');
  }

  // Get minimal file tree (only path to the file)
  const minimalTree = await getMinimalFileTree(repositoryId, file_path);

  // If minimal tree is null, try to find file directly
  let selectedFile;
  if (minimalTree) {
    selectedFile = findFileInTree(minimalTree, file_path);
  } else {
    // Fallback: get full tree and find file
    const fullTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
      includeChanges: false
    });
    selectedFile = findFileInTree(fullTree, file_path);
  }
  
  if (!selectedFile) {
    throw new Error('File not found: ' + file_path);
  }

  // Get absolute path from database for file reading
  const fileRecord = await fileStorageService.getFileByPath(repositoryId, file_path);
  if (!fileRecord) {
    throw new Error('File record not found in database: ' + file_path);
  }

  // Read file content using absolute path from database
  const content = await fileStorageService.readFileContentForAI(fileRecord.absolute_path, fileRecord.is_binary);

  // Remove absolute_path from file_info for security
  const { absolute_path, ...fileInfoWithoutAbsolutePath } = selectedFile;
  
  return {
    left_panel: {
      file_tree: minimalTree,
      summary: {
        total_files_changed: 0,
        total_additions: 0,
        total_deletions: 0
      }
    },
    right_panel: {
      file_info: {
        ...fileInfoWithoutAbsolutePath,
        content: content
      },
      diff_data: { changes: [] }
    }
  };
}

// Helper function to find file in tree
function findFileInTree(tree, targetPath) {
  
  if (tree.type === 'file') {
    
    // Exact match
    if (tree.path === targetPath) {
      return tree;
    }
    
    // Check if the target path ends with the file path (for relative paths)
    if (tree.path.endsWith(targetPath)) {
      return tree;
    }
    
    // Check if the file name matches (for simple file names)
    const fileName = tree.name;
    const targetFileName = targetPath.split('/').pop();
    if (fileName === targetFileName) {
      return tree;
    }
  }
  
  if (tree.children) {
    for (const child of tree.children) {
      const found = findFileInTree(child, targetPath);
      if (found) return found;
    }
  }
  
  return null;
}

// Helper function to find first changed file
function findFirstChangedFile(tree) {
  if (tree.type === 'file' && tree.change_status) {
    return tree;
  }
  
  if (tree.children) {
    for (const child of tree.children) {
      const found = findFirstChangedFile(child);
      if (found) return found;
    }
  }
  
  return null;
}

// Debug endpoint to check repository data
router.get('/:provider/repository/:id/debug', async (req, res) => {
  try {
    const { provider, id } = req.params;
    const providerKey = provider.toLowerCase();
    
    let tableName;
    switch (providerKey) {
      case 'github':
        tableName = 'all_repositories';
        break;
      case 'gitlab':
        tableName = 'gitlab_repositories';
        break;
      case 'bitbucket':
        tableName = 'bitbucket_repositories';
        break;
      case 'gitea':
        tableName = 'gitea_repositories';
        break;
    }

    // Get repository info
    const repoQuery = `SELECT * FROM ${tableName} WHERE id = $1`;
    const repoResult = await database.query(repoQuery, [id]);
    
    if (repoResult.rows.length === 0) {
      return res.status(404).json({ error: 'Repository not found' });
    }

    // Get directories
    const dirsQuery = `
      SELECT rd.*, COUNT(rf.id) as files_count
      FROM repository_directories rd
      LEFT JOIN repository_files rf ON rd.id = rf.directory_id
      WHERE rd.repository_id = $1
      GROUP BY rd.id
      ORDER BY rd.relative_path
    `;
    const dirsResult = await database.query(dirsQuery, [id]);

    // Get files
    const filesQuery = `
      SELECT rf.*, rd.relative_path as directory_path
      FROM repository_files rf
      LEFT JOIN repository_directories rd ON rf.directory_id = rd.id
      WHERE rf.repository_id = $1
      ORDER BY rf.relative_path
    `;
    const filesResult = await database.query(filesQuery, [id]);

    res.json({
      repository: repoResult.rows[0],
      directories: dirsResult.rows,
      files: filesResult.rows,
      counts: {
        directories: dirsResult.rows.length,
        files: filesResult.rows.length
      }
    });

  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});


// Helper function to get minimal file tree for blob view
async function getMinimalFileTree(repositoryId, filePath) {
  try {
    // Get the directory path of the file
    const pathParts = filePath.split('/');
    const fileName = pathParts.pop();
    const directoryPath = pathParts.join('/');
    
    // Get full file tree
    const fullTree = await fileStorageService.getRepositoryFileTree(repositoryId, {
      includeChanges: false
    });
    
    // Find the file in the full tree
    const targetFile = findFileInTree(fullTree, filePath);
    
    if (!targetFile) {
      console.warn('File not found in tree:', filePath);
      return null;
    }
    
    // If no directory path, return just the file
    if (!directoryPath) {
      return {
        type: 'directory',
        name: 'root',
        path: '',
        level: -1,
        children: [targetFile]
      };
    }
    
    // Find the directory containing the file
    const targetDir = findDirectoryByPath(fullTree, directoryPath);
    
    if (targetDir) {
      return {
        type: 'directory',
        name: 'root',
        path: '',
        level: -1,
        children: [targetDir]
      };
    }
    
    // Fallback: return minimal tree with just the file
    return {
      type: 'directory',
      name: 'root',
      path: '',
      level: -1,
      children: [targetFile]
    };
    
  } catch (error) {
    console.warn('Error getting minimal file tree:', error.message);
    // Return null tree as fallback
    return null;
  }
}


module.exports = router;