// services/providers/gitea.adapter.js
const VcsProviderInterface = require('../vcs-provider.interface');
const FileStorageService = require('../file-storage.service');
const GitRepoService = require('../git-repo.service');
const GiteaOAuthService = require('../gitea-oauth');
const axios = require('axios');
const https = require('https');

class GiteaAdapter extends VcsProviderInterface {
  constructor() {
    super();
    this.fileStorageService = new FileStorageService();
    this.gitRepoService = new GitRepoService();
    this.host = process.env.GITEA_BASE_URL || 'gitea.com';
    this.oauth = new GiteaOAuthService();
  }

  parseRepoUrl(url) {
    if (!url || typeof url !== 'string') throw new Error('URL must be a non-empty string');
    let normalized = url.trim();
    if (!normalized.startsWith('http')) normalized = 'https://' + normalized;
    const host = normalized.replace(/^https?:\/\//, '').split('/')[0];
    // Gitea can be self-hosted; accept any host when explicitly using /api/vcs/gitea
    const parts = normalized.split(host)[1].replace(/^\//, '').split('#')[0].split('?')[0].split('/');
    const owner = parts[0];
    const repo = (parts[1] || '').replace(/\.git$/, '');
    if (!owner || !repo) throw new Error(`Invalid Gitea repository URL: ${url}`);
    let branch = 'main';
    const treeIdx = parts.findIndex(p => p === 'tree');
    if (treeIdx >= 0 && parts[treeIdx + 1]) branch = parts[treeIdx + 1];
    return { owner, repo, branch };
  }

  async checkRepositoryAccess(owner, repo) {
    const token = await this.oauth.getToken();
    const base = (process.env.GITEA_BASE_URL || 'https://gitea.com').replace(/\/$/, '');
    
    console.log(`🔍 [GITEA] Checking repository access for: ${owner}/${repo}`);
    console.log(`🔍 [GITEA] Token available: ${!!token?.access_token}`);
    console.log(`🔍 [GITEA] API base URL: ${base}`);
    
    try {
      // Always try with authentication first (like GitHub behavior)
      if (token?.access_token) {
        const url = `${base}/api/v1/repos/${owner}/${repo}`;
        console.log(`🔍 [GITEA] Trying authenticated request to: ${url}`);
        
        const response = await axios.get(url, {
          headers: { Authorization: `token ${token.access_token}` },
          httpsAgent: new https.Agent({
            keepAlive: true,
            timeout: 15000,
            family: 4 // Force IPv4 to avoid IPv6 connectivity issues
          }),
          timeout: 15000,
          validateStatus: function (status) {
            return status >= 200 && status < 300; // Only consider 2xx as success
          }
        });
        
        console.log(`🔍 [GITEA] Authenticated response status: ${response.status}`);
        
        if (response.status === 200) {
          const d = response.data;
          const isPrivate = !!d.private;
          console.log(`✅ [GITEA] Repository accessible via authentication, private: ${isPrivate}`);
          return { exists: true, isPrivate, hasAccess: true, requiresAuth: isPrivate };
        } else {
          console.log(`❌ [GITEA] Authenticated request failed with status: ${response.status}`);
          console.log(`❌ [GITEA] Error response: ${JSON.stringify(response.data)}`);
        }
      }
      
      // No token or token failed: try without authentication
      const url = `${base}/api/v1/repos/${owner}/${repo}`;
      console.log(`🔍 [GITEA] Trying unauthenticated request to: ${url}`);
      
      const response = await axios.get(url, {
        httpsAgent: new https.Agent({
          keepAlive: true,
          timeout: 15000,
          family: 4 // Force IPv4 to avoid IPv6 connectivity issues
        }),
        timeout: 15000,
        validateStatus: function (status) {
          return status >= 200 && status < 300; // Only consider 2xx as success
        }
      });
      
      console.log(`🔍 [GITEA] Unauthenticated response status: ${response.status}`);
      
      if (response.status === 200) {
        const d = response.data;
        console.log(`✅ [GITEA] Repository accessible without authentication, private: ${!!d.private}`);
        return { exists: true, isPrivate: !!d.private, hasAccess: true, requiresAuth: false };
      }
    } catch (error) {
      if (error.response) {
        // The request was made and the server responded with a status code
        // that falls out of the range of 2xx
        console.log(`❌ [GITEA] Request failed with status: ${error.response.status}`);
        console.log(`❌ [GITEA] Error response: ${JSON.stringify(error.response.data)}`);
        
        if (error.response.status === 404 || error.response.status === 403) {
          console.log(`🔍 [GITEA] Repository exists but requires authentication (status: ${error.response.status})`);
          return { exists: error.response.status !== 404 ? true : false, isPrivate: true, hasAccess: false, requiresAuth: true };
        }
      } else if (error.request) {
        // The request was made but no response was received
        console.log(`❌ [GITEA] Network error: ${error.message}`);
      } else {
        // Something happened in setting up the request that triggered an Error
        console.log(`❌ [GITEA] Exception occurred: ${error.message}`);
      }
      
      // If any error occurs, assume repository requires authentication
      return { exists: false, isPrivate: null, hasAccess: false, requiresAuth: true, error: 'Repository not found or requires authentication' };
    }
    
    console.log(`❌ [GITEA] Falling through to default error case`);
    return { exists: false, isPrivate: null, hasAccess: false, requiresAuth: true, error: 'Repository not found or requires authentication' };
  }

  async fetchRepositoryMetadata(owner, repo) {
    const token = await this.oauth.getToken();
    const base = (process.env.GITEA_BASE_URL || 'https://gitea.com').replace(/\/$/, '');
    if (token?.access_token) {
      try {
        const response = await axios.get(`${base}/api/v1/repos/${owner}/${repo}`, {
          headers: { Authorization: `token ${token.access_token}` },
          httpsAgent: new https.Agent({
            keepAlive: true,
            timeout: 15000,
            family: 4 // Force IPv4 to avoid IPv6 connectivity issues
          }),
          timeout: 15000
        });
        if (response.status === 200) {
          const d = response.data;
          return { full_name: d.full_name || `${owner}/${repo}`, visibility: d.private ? 'private' : 'public', default_branch: d.default_branch || 'main', updated_at: d.updated_at };
        }
      } catch (error) {
        console.log(`❌ [GITEA] Failed to fetch repository metadata: ${error.message}`);
      }
    }
    return { full_name: `${owner}/${repo}`, visibility: 'public', default_branch: 'main', updated_at: new Date().toISOString() };
  }

  async analyzeCodebase(owner, repo, branch) {
    return { total_files: 0, total_size: 0, directories: [], branch };
  }

  async ensureRepositoryWebhook(owner, repo, callbackUrl) {
    try {
      if (!callbackUrl) {
        console.warn('⚠️ [GITEA] Webhook callbackUrl not provided; skipping webhook creation');
        return { created: false, reason: 'missing_callback_url' };
      }
      
      const token = await this.oauth.getToken();
      if (!token?.access_token) {
        console.warn('⚠️ [GITEA] OAuth token not available; skipping webhook creation');
        return { created: false, reason: 'missing_token' };
      }
      
      const base = (process.env.GITEA_BASE_URL || 'https://gitea.com').replace(/\/$/, '');
      const secret = process.env.GITEA_WEBHOOK_SECRET || '';
      
      console.log(`🔗 [GITEA] Setting up webhook for ${owner}/${repo}`);
      
      // First, list existing hooks to avoid duplicates
      try {
        const listResponse = await axios.get(`${base}/api/v1/repos/${owner}/${repo}/hooks`, {
          headers: { Authorization: `token ${token.access_token}` },
          httpsAgent: new https.Agent({
            keepAlive: true,
            timeout: 15000,
            family: 4
          }),
          timeout: 15000
        });
        
        if (listResponse.status === 200) {
          const existingHooks = listResponse.data;
          
          // Check if a webhook with our callback URL already exists
          const existingHook = existingHooks.find(hook => 
            hook.config && hook.config.url === callbackUrl
          );
          
          if (existingHook) {
            console.log(`✅ [GITEA] Webhook already exists (ID: ${existingHook.id})`);
            return { created: false, reason: 'already_exists', hook_id: existingHook.id };
          }
        }
      } catch (error) {
        console.warn('⚠️ [GITEA] Could not list existing webhooks, continuing with creation attempt:', error.message);
      }
      
      // Create new webhook
      const response = await axios.post(`${base}/api/v1/repos/${owner}/${repo}/hooks`, {
        type: 'gitea',
        config: {
          url: callbackUrl,
          content_type: 'json',
          secret: secret || undefined
        },
        events: ['push'],
        active: true
      }, {
        headers: { 
          'Content-Type': 'application/json', 
          Authorization: `token ${token.access_token}` 
        },
        httpsAgent: new https.Agent({
          keepAlive: true,
          timeout: 15000,
          family: 4
        }),
        timeout: 15000
      });
      
      if (response.status === 200 || response.status === 201) { 
        const hookData = response.data; 
        console.log(`✅ [GITEA] Webhook created successfully (ID: ${hookData.id})`);
        return { created: true, hook_id: hookData.id }; 
      }
      
      console.warn(`⚠️ [GITEA] Webhook creation failed with status: ${response.status}`);
      return { created: false, reason: `status_${response.status}` };
      
    } catch (error) {
      // Common cases: insufficient permissions, private repo without correct scope
      if (error.response) {
        console.warn('⚠️ [GITEA] Webhook creation failed:', error.response.status, error.response.data?.message || error.message);
        return { created: false, error: error.message, status: error.response.status };
      } else {
        console.warn('⚠️ [GITEA] Webhook creation failed:', error.message);
        return { created: false, error: error.message };
      }
    }
  }

  async syncRepositoryWithGit(owner, repo, branch, repositoryId) {
    const database = require('../../config/database');
    let storageRecord = null;
    try {
      const token = await this.oauth.getToken();
      let repoPath = null;
      if (token?.access_token) {
        repoPath = await this.gitRepoService.cloneIfMissingWithAuth(owner, repo, branch, this.host, token.access_token, 'oauth2');
      } else {
        repoPath = await this.gitRepoService.cloneIfMissingWithHost(owner, repo, branch, this.host);
      }
      // Fetch and fast-forward to ensure latest commits are present
      let beforeSha = await this.gitRepoService.getHeadSha(repoPath);
      let afterSha = beforeSha;
      try {
        const res = await this.gitRepoService.fetchAndFastForward(repoPath, branch);
        beforeSha = res.beforeSha || beforeSha;
        afterSha = res.afterSha || afterSha;
      } catch (_) {}

      storageRecord = await this.fileStorageService.initializeRepositoryStorage(repositoryId, repoPath);
      await this.fileStorageService.processDirectoryStructure(storageRecord.id, repositoryId, repoPath);
      const finalStorage = await this.fileStorageService.completeRepositoryStorage(storageRecord.id);
      
      // Get the current HEAD commit SHA and update the repository record
      try {
        const headSha = afterSha || (await this.gitRepoService.getHeadSha(repoPath));
        await database.query(
          'UPDATE all_repositories SET last_synced_at = NOW(), last_synced_commit_sha = $1, updated_at = NOW() WHERE id = $2',
          [headSha, repositoryId]
        );
      } catch (e) {
        // If we can't get the SHA, still update the sync time
        await database.query(
          'UPDATE all_repositories SET last_synced_at = NOW(), updated_at = NOW() WHERE id = $1',
          [repositoryId]
        );
      }
      return { success: true, method: 'git', targetDir: repoPath, beforeSha, afterSha, storage: finalStorage };
    } catch (e) {
      if (storageRecord) await this.fileStorageService.markStorageFailed(storageRecord.id, e.message);
      return { success: false, error: e.message };
    }
  }

  async downloadRepositoryWithStorage() {
    return { success: false, error: 'api_download_not_implemented' };
  }

  async syncRepositoryWithFallback(owner, repo, branch, repositoryId) {
    const git = await this.syncRepositoryWithGit(owner, repo, branch, repositoryId);
    if (git.success) return git;
    return { success: false, error: git.error };
  }

  async getRepositoryDiff(owner, repo, branch, fromSha, toSha) {
    const repoPath = this.gitRepoService.getLocalRepoPath(owner, repo, branch);
    // Proactively fetch latest to ensure SHAs exist
    try { await this.gitRepoService.fetchAndFastForward(repoPath, branch); } catch (_) {}
    return await this.gitRepoService.getDiff(repoPath, fromSha || null, toSha || 'HEAD', { patch: true });
  }

  async getRepositoryChangesSince(owner, repo, branch, sinceSha) {
    const repoPath = this.gitRepoService.getLocalRepoPath(owner, repo, branch);
    try { await this.gitRepoService.fetchAndFastForward(repoPath, branch); } catch (_) {}
    return await this.gitRepoService.getChangedFilesSince(repoPath, sinceSha);
  }

  async cleanupRepositoryStorage(repositoryId) {
    return await this.fileStorageService.cleanupRepositoryStorage(repositoryId);
  }
}

module.exports = GiteaAdapter;


