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

class GitlabAdapter extends VcsProviderInterface {
  constructor() {
    super();
    this.fileStorageService = new FileStorageService();
    this.gitRepoService = new GitRepoService();
    this.host = process.env.GITLAB_BASE_URL || 'gitlab.com';
    this.oauth = new GitLabOAuthService();
  }

  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];
    if (!host.includes('gitlab')) throw new Error(`Invalid GitLab repository URL: ${url}`);
    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 GitLab 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.GITLAB_BASE_URL || 'https://gitlab.com').replace(/\/$/, '');
    
    try {
      // Always try with authentication first (like GitHub behavior)
      if (token?.access_token) {
        const resp = await fetch(`${base}/api/v4/projects/${encodeURIComponent(`${owner}/${repo}`)}`, { headers: { Authorization: `Bearer ${token.access_token}` } });
        if (resp.status === 200) {
          const data = await resp.json();
          return { exists: true, isPrivate: data.visibility !== 'public', hasAccess: true, requiresAuth: data.visibility !== 'public' };
        }
      }
      
      // No token or token failed: try without authentication
      const resp = await fetch(`${base}/api/v4/projects/${encodeURIComponent(`${owner}/${repo}`)}`);
      if (resp.status === 200) {
        const data = await resp.json();
        return { exists: true, isPrivate: data.visibility !== 'public', hasAccess: true, requiresAuth: false };
      }
      if (resp.status === 404 || resp.status === 403) {
        // Repository exists but requires authentication (like GitHub behavior)
        return { exists: resp.status !== 404 ? true : false, isPrivate: true, hasAccess: false, requiresAuth: true };
      }
    } catch (error) {
      // If any error occurs, assume repository requires authentication
      return { exists: false, isPrivate: null, hasAccess: false, requiresAuth: true, error: 'Repository not found or requires authentication' };
    }
    
    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.GITLAB_BASE_URL || 'https://gitlab.com').replace(/\/$/, '');
    if (token?.access_token) {
      try {
        const resp = await fetch(`${base}/api/v4/projects/${encodeURIComponent(`${owner}/${repo}`)}`, { headers: { Authorization: `Bearer ${token.access_token}` } });
        if (resp.ok) {
          const d = await resp.json();
          return { full_name: d.path_with_namespace, visibility: d.visibility === 'public' ? 'public' : 'private', default_branch: d.default_branch || 'main', updated_at: d.last_activity_at };
        }
      } catch (_) {}
    }
    return { full_name: `${owner}/${repo}`, visibility: 'public', default_branch: 'main', updated_at: new Date().toISOString() };
  }

  async analyzeCodebase(owner, repo, branch) {
    // Not using API; actual analysis happens after sync in storage
    return { total_files: 0, total_size: 0, directories: [], branch };
  }

  async ensureRepositoryWebhook(owner, repo, callbackUrl) {
    try {
      if (!callbackUrl) return { created: false, reason: 'missing_callback_url' };
      const token = await this.oauth.getToken();
      if (!token?.access_token) return { created: false, reason: 'missing_token' };
      const base = (process.env.GITLAB_BASE_URL || 'https://gitlab.com').replace(/\/$/, '');
      const secret = process.env.GITLAB_WEBHOOK_SECRET || '';
      const resp = await fetch(`${base}/api/v4/projects/${encodeURIComponent(`${owner}/${repo}`)}/hooks`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token.access_token}` },
        body: JSON.stringify({ url: callbackUrl, push_events: true, token: secret || undefined, enable_ssl_verification: true })
      });
      if (resp.ok) { const data = await resp.json(); return { created: true, hook_id: data.id }; }
      return { created: false, reason: `status_${resp.status}` };
    } catch (e) {
      return { created: false, error: e.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;
      
      // Always try with authentication first for GitLab, even for public repos
      // because GitLab often requires auth for git operations
      if (token?.access_token) {
        repoPath = await this.gitRepoService.cloneIfMissingWithAuth(owner, repo, branch, this.host, token.access_token, 'oauth2');
      } else {
        // If no token, try without auth first, but if it fails, require authentication
        try {
          repoPath = await this.gitRepoService.cloneIfMissingWithHost(owner, repo, branch, this.host);
        } catch (cloneError) {
          // If clone fails without auth, this means the repo requires authentication
          throw new Error(`GitLab repository requires authentication: ${cloneError.message}`);
        }
      }
      
      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 = await this.gitRepoService.getHeadSha(repoPath);
        await database.query(
          'UPDATE github_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 github_repositories SET last_synced_at = NOW(), updated_at = NOW() WHERE id = $1',
          [repositoryId]
        );
      }
      return { success: true, method: 'git', targetDir: repoPath, storage: finalStorage };
    } catch (e) {
      if (storageRecord) await this.fileStorageService.markStorageFailed(storageRecord.id, e.message);
      return { success: false, error: e.message };
    }
  }

  async downloadRepositoryWithStorage(owner, repo, branch, repositoryId) {
    // Not implemented for GitLab without API token; fallback to git
    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);
    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);
    return await this.gitRepoService.getChangedFilesSince(repoPath, sinceSha);
  }

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

module.exports = GitlabAdapter;


