// services/webhook.service.js
const crypto = require('crypto');
const database = require('../config/database');
const GitHubIntegrationService = require('./github-integration.service');
const DiffProcessingService = require('./diff-processing.service');

class WebhookService {
  constructor() {
    this.webhookSecret = process.env.GITHUB_WEBHOOK_SECRET || 'default-webhook-secret';
    this._schemaChecked = false;
    this._webhookEventColumns = new Map();
    this.githubService = new GitHubIntegrationService();
    this.diffService = new DiffProcessingService();
  }

  // Verify GitHub webhook signature
  verifySignature(payload, signature) {
    if (!signature) {
      return false;
    }

    const expectedSignature = crypto
      .createHmac('sha256', this.webhookSecret)
      .update(payload)
      .digest('hex');

    const providedSignature = signature.replace('sha256=', '');
    
    return crypto.timingSafeEqual(
      Buffer.from(expectedSignature, 'hex'),
      Buffer.from(providedSignature, 'hex')
    );
  }

  // Process GitHub webhook events
  async processWebhookEvent(eventType, payload) {
    console.log(`Processing GitHub webhook event: ${eventType}`);
    
    try {
      switch (eventType) {
        case 'push':
          await this.handlePushEvent(payload);
          break;
        case 'pull_request':
          await this.handlePullRequestEvent(payload);
          break;
        case 'repository':
          await this.handleRepositoryEvent(payload);
          break;
        case 'ping':
          await this.handlePingEvent(payload);
          break;
        default:
          console.log(`Unhandled webhook event type: ${eventType}`);
      }
    } catch (error) {
      console.error(`Error processing webhook event ${eventType}:`, error);
      throw error;
    }
  }

  // Handle push events
  async handlePushEvent(payload) {
    const { repository, ref, commits, pusher } = payload;
    
    console.log(`Push event received for ${repository.full_name} on ${ref}`);
    console.log(`Pusher: ${pusher.name}, Commits: ${commits.length}`);

    // Persist raw webhook and commit SHAs
    try {
      const repoOwner = repository.owner.login;
      const repoName = repository.name;
      const branchFromRef = (ref || '').startsWith('refs/heads/') ? ref.replace('refs/heads/', '') : (repository.default_branch || 'main');

      // Find repository_id in our DB if attached
      const repoLookup = await database.query(
        'SELECT id FROM all_repositories WHERE owner_name = $1 AND repository_name = $2 ORDER BY created_at DESC LIMIT 1',
        [repoOwner, repoName]
      );
      const repoId = repoLookup.rows[0]?.id || null;

      // Insert into durable github_webhooks table
      await database.query(
        `INSERT INTO github_webhooks (delivery_id, event_type, action, owner_name, repository_name, repository_id, ref, before_sha, after_sha, commit_count, payload)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
        [
          payload.delivery_id || null, // may be null if not provided by route; route passes header separately
          'push',
          null,
          repoOwner,
          repoName,
          repoId,
          ref,
          payload.before || null,
          payload.after || null,
          Array.isArray(commits) ? commits.length : 0,
          JSON.stringify(payload)
        ]
      );

      if (repoId) {
        // repository_commit_events table removed as requested

        // Persist per-commit details and file paths (added/modified/removed)
        if (Array.isArray(commits) && commits.length > 0) {
          for (const commit of commits) {
            try {
              const commitInsert = await database.query(
                `INSERT INTO repository_commit_details (repository_id, commit_sha, author_name, author_email, message, url)
                 VALUES ($1, $2, $3, $4, $5, $6)
                 ON CONFLICT (repository_id, commit_sha) DO UPDATE SET
                   author_name = EXCLUDED.author_name,
                   author_email = EXCLUDED.author_email,
                   message = EXCLUDED.message,
                   url = EXCLUDED.url
                 RETURNING id`,
                [
                  repoId,
                  commit.id,
                  commit.author?.name || null,
                  commit.author?.email || null,
                  commit.message || null,
                  commit.url || null
                ]
              );

              const commitId = commitInsert.rows[0].id;

              const addFiles = (paths = [], changeType) => paths.forEach(async (p) => {
                try {
                  await database.query(
                    `INSERT INTO repository_commit_files (commit_id, change_type, file_path)
                     VALUES ($1, $2, $3)`,
                    [commitId, changeType, p]
                  );
                } catch (_) {}
              });

              addFiles(commit.added || [], 'added');
              addFiles(commit.modified || [], 'modified');
              addFiles(commit.removed || [], 'removed');
            } catch (commitErr) {
              console.warn('Failed to persist commit details:', commitErr.message);
            }
          }
        }

        // Kick off background re-sync to refresh local files and DB (git-based)
        setImmediate(async () => {
          try {
            // Mark syncing
            await database.query(
              'UPDATE all_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
              ['syncing', repoId]
            );

            // Clean existing storage then git-sync and re-index
            await this.githubService.cleanupRepositoryStorage(repoId);
            const downloadResult = await this.githubService.syncRepositoryWithFallback(
              repoOwner,
              repoName,
              branchFromRef,
              repoId
            );

            // Process diffs for each commit after successful sync
            if (downloadResult.success && downloadResult.targetDir) {
              await this.processCommitDiffs(repoId, commits, downloadResult.targetDir);
            }

            await database.query(
              'UPDATE all_repositories SET sync_status = $1, last_synced_at = NOW(), updated_at = NOW() WHERE id = $2',
              [downloadResult.success ? 'synced' : 'error', repoId]
            );
          } catch (syncErr) {
            console.warn('Auto-sync failed:', syncErr.message);
            try {
              await database.query(
                'UPDATE all_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
                ['error', repoId]
              );
            } catch (_) {}
          }
        });
      }
    } catch (e) {
      console.warn('Failed to persist push webhook details:', e.message);
    }

    // Find repositories in our database that match this GitHub repository
    const query = `
      SELECT gr.*, rs.storage_status, rs.local_path
      FROM all_repositories gr
      LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
      WHERE gr.owner_name = $1 AND gr.repository_name = $2
    `;
    
    const result = await database.query(query, [repository.owner.login, repository.name]);
    
    if (result.rows.length > 0) {
      console.log(`Found ${result.rows.length} matching repositories in database`);
      
      // Update last synced timestamp
      for (const repo of result.rows) {
        await database.query(
          'UPDATE all_repositories SET last_synced_at = NOW(), updated_at = NOW() WHERE id = $1',
          [repo.id]
        );
        
        // If repository is synced, we could trigger a re-sync here
        if (repo.storage_status === 'completed') {
          console.log(`Repository ${repo.repository_name} is synced, could trigger re-sync`);
          // You could add logic here to trigger a background sync
        }
      }
    } else {
      console.log(`No matching repositories found for ${repository.full_name}`);
    }
  }

  // Handle pull request events
  async handlePullRequestEvent(payload) {
    const { action, pull_request, repository } = payload;
    
    console.log(`Pull request ${action} for ${repository.full_name}: #${pull_request.number}`);
    console.log(`PR Title: ${pull_request.title}`);
    console.log(`PR State: ${pull_request.state}`);

    // Log PR events for potential future integration
    await this.logWebhookEvent('pull_request', action, repository.full_name, {
      pr_number: pull_request.number,
      pr_title: pull_request.title,
      pr_state: pull_request.state,
      pr_url: pull_request.html_url
    });
  }

  // Handle repository events
  async handleRepositoryEvent(payload) {
    const { action, repository } = payload;
    
    console.log(`Repository ${action} event for ${repository.full_name}`);
    
    if (action === 'deleted') {
      // Handle repository deletion
      const query = `
        SELECT gr.id, gr.template_id
        FROM all_repositories gr
        WHERE gr.owner_name = $1 AND gr.repository_name = $2
      `;
      
      const result = await database.query(query, [repository.owner.login, repository.name]);
      
      if (result.rows.length > 0) {
        console.log(`Repository ${repository.full_name} was deleted, marking as inactive`);
        await database.query(
          'UPDATE all_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
          ['deleted', result.rows[0].id]
        );
      }
    }
  }

  // Handle ping events (GitHub webhook test)
  async handlePingEvent(payload) {
    console.log('GitHub webhook ping received - webhook is working correctly');
    console.log(`Repository: ${payload.repository?.full_name || 'Unknown'}`);
    console.log(`Zen: ${payload.zen || 'No zen message'}`);
  }

  // Process diffs for commits in a push event
  async processCommitDiffs(repositoryId, commits, repoLocalPath) {
    console.log(`🔄 Processing diffs for ${commits.length} commits in repository ${repositoryId}`);
    console.log(`📁 Repository local path: ${repoLocalPath}`);
    
    if (!Array.isArray(commits) || commits.length === 0) {
      console.log('⚠️  No commits to process');
      return;
    }

    for (const commit of commits) {
      try {
        console.log(`📝 Processing diff for commit: ${commit.id}`);
        console.log(`📝 Commit message: ${commit.message}`);
        console.log(`📝 Commit author: ${commit.author?.name}`);
        
        // Get commit record from database
        const commitQuery = `
          SELECT id FROM repository_commit_details 
          WHERE repository_id = $1 AND commit_sha = $2
        `;
        
        const commitResult = await database.query(commitQuery, [repositoryId, commit.id]);
        
        if (commitResult.rows.length === 0) {
          console.warn(`⚠️  Commit ${commit.id} not found in database, skipping diff processing`);
          continue;
        }
        
        const commitId = commitResult.rows[0].id;
        console.log(`📝 Found commit record with ID: ${commitId}`);
        
        // Get parent commit SHA for diff calculation
        const parentSha = commit.parents && commit.parents.length > 0 ? commit.parents[0].id : null;
        console.log(`📝 Parent SHA: ${parentSha}`);
        
        // Check if repository path exists
        const fs = require('fs');
        if (!fs.existsSync(repoLocalPath)) {
          console.error(`❌ Repository path does not exist: ${repoLocalPath}`);
          continue;
        }
        
        // Check if .git directory exists
        const gitPath = require('path').join(repoLocalPath, '.git');
        if (!fs.existsSync(gitPath)) {
          console.error(`❌ Git directory does not exist: ${gitPath}`);
          continue;
        }
        
        console.log(`📝 Repository path verified, processing diff...`);
        
        // Process the diff
        const diffResult = await this.diffService.processCommitDiffs(
          commitId,
          repositoryId,
          repoLocalPath,
          parentSha,
          commit.id
        );
        
        if (diffResult.success) {
          console.log(`✅ Successfully processed ${diffResult.processedFiles} file diffs for commit ${commit.id}`);
        } else {
          console.warn(`⚠️  Failed to process diffs for commit ${commit.id}: ${diffResult.error || diffResult.reason}`);
        }
        
      } catch (error) {
        console.error(`❌ Error processing diff for commit ${commit.id}:`, error.message);
        console.error(`❌ Error stack:`, error.stack);
      }
    }
  }

  // webhook_events table removed as requested - logging functionality disabled
}

module.exports = WebhookService;
