// 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 github_repositories for now
    const insertQuery = `
      INSERT INTO github_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.file_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 github_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, '[]', '{}');
      }
      await database.query(
        `INSERT INTO feature_codebase_mappings (id, feature_id, repository_id, code_paths, code_snippets) VALUES ${mappingValues.join(', ')}`,
        params
      );
    }

    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 = 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 provided = req.headers['x-gitea-signature'];
          const secret = process.env.GITEA_WEBHOOK_SECRET;
          if (!secret) return true;
          if (!provided) return false;
          const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
          return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(provided, 'hex'));
        }
        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' });
    }

    // 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 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 } = 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 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 } = 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 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;
    };
    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 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 } });
  } 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 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, 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.*, 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 (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 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 (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 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 } = 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 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 (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 github_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 feature_codebase_mappings WHERE repository_id = $1', [id]);
    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 (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', (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;
      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) {
          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 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) {
      res.status(500).json({ success: false, message: e.message || 'OAuth callback failed' });
    }
  })();
});

