// github-oauth.js
const { Octokit } = require('@octokit/rest');
const database = require('../config/database');

class GitHubOAuthService {
  constructor() {
    this.clientId = process.env.GITHUB_CLIENT_ID;
    this.clientSecret = process.env.GITHUB_CLIENT_SECRET;
    this.redirectUri = process.env.GITHUB_REDIRECT_URI || 'http://localhost:8010/api/github/auth/github/callback';
    
    if (!this.clientId || !this.clientSecret) {
      console.warn('GitHub OAuth not configured. Only public repositories will be accessible.');
    }
  }

  // Generate GitHub OAuth URL
  getAuthUrl(state, userId = null) {
    if (!this.clientId) {
      throw new Error('GitHub OAuth not configured');
    }

    // If a userId is provided, append it to the redirect_uri so the callback can link token to that user
    let redirectUri = this.redirectUri;
    if (userId) {
      const hasQuery = redirectUri.includes('?');
      redirectUri = `${redirectUri}${hasQuery ? '&' : '?'}user_id=${encodeURIComponent(userId)}`;
    }

    // Also embed userId into the OAuth state for fallback extraction in callback
    const stateWithUser = userId ? `${state}|uid=${userId}` : state;

    const params = new URLSearchParams({
      client_id: this.clientId,
      redirect_uri: redirectUri,
      scope: 'repo,user:email',
      state: stateWithUser,
      allow_signup: 'false'
    });

    return `https://github.com/login/oauth/authorize?${params.toString()}`;
  }

  // Generate auth URL for a specific user (wrapper method)
  async generateAuthUrl(userId) {
    const state = Math.random().toString(36).substring(7);
    return this.getAuthUrl(state, userId);
  }

  // Exchange authorization code for access token
  async exchangeCodeForToken(code) {
    const response = await fetch('https://github.com/login/oauth/access_token', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        client_id: this.clientId,
        client_secret: this.clientSecret,
        code: code,
      }),
    });

    const data = await response.json();
    
    if (data.error) {
      throw new Error(`OAuth error: ${data.error_description}`);
    }

    return data.access_token;
  }

  // Get user info from GitHub
  async getUserInfo(accessToken) {
    const octokit = new Octokit({ auth: accessToken });
    const { data: user } = await octokit.users.getAuthenticated();
    return user;
  }

  // Store GitHub token with user ID
  async storeToken(accessToken, githubUser, userId = null) {
    const query = `
      INSERT INTO github_user_tokens (access_token, github_username, github_user_id, scopes, expires_at, user_id, is_primary)
      VALUES ($1, $2, $3, $4, $5, $6, $7)
      ON CONFLICT (user_id, github_username) 
      DO UPDATE SET 
        access_token = $1,
        github_user_id = $3,
        scopes = $4,
        expires_at = $5,
        is_primary = $7,
        updated_at = NOW()
      RETURNING *
    `;

    // If this is the first GitHub account for the user, make it primary
    const isPrimary = userId ? await this.isFirstGitHubAccountForUser(userId) : false;

    const result = await database.query(query, [
      accessToken,
      githubUser.login,
      githubUser.id,
      JSON.stringify(['repo', 'user:email']),
      null,
      userId,
      isPrimary
    ]);

    return result.rows[0];
  }

  // Check if this is the first GitHub account for a user
  async isFirstGitHubAccountForUser(userId) {
    const result = await database.query(
      'SELECT COUNT(*) as count FROM github_user_tokens WHERE user_id = $1',
      [userId]
    );
    return parseInt(result.rows[0].count) === 0;
  }

  // Get stored token (legacy method - gets any token)
  async getToken() {
    const query = 'SELECT * FROM github_user_tokens ORDER BY created_at DESC LIMIT 1';
    const result = await database.query(query);
    return result.rows[0];
  }

  // Get all tokens for a specific user
  async getUserTokens(userId) {
    const query = 'SELECT * FROM github_user_tokens WHERE user_id = $1 ORDER BY is_primary DESC, created_at DESC';
    const result = await database.query(query, [userId]);
    return result.rows;
  }

  // Get primary token for a user
  async getUserPrimaryToken(userId) {
    const query = 'SELECT * FROM github_user_tokens WHERE user_id = $1 AND is_primary = true LIMIT 1';
    const result = await database.query(query, [userId]);
    return result.rows[0] || null;
  }

  // Find the right token for accessing a specific repository
  async findTokenForRepository(userId, owner, repo) {
    const tokens = await this.getUserTokens(userId);
    
    for (const token of tokens) {
      try {
        const octokit = new Octokit({ auth: token.access_token });
        // Try to access the repository with this token
        await octokit.repos.get({ owner, repo });
        console.log(`✅ Found token for ${owner}/${repo}: ${token.github_username}`);
        return token;
      } catch (error) {
        console.log(`❌ Token ${token.github_username} cannot access ${owner}/${repo}: ${error.message}`);
        continue;
      }
    }
    
    return null; // No token found that can access this repository
  }

  // Validate if a token is still valid
  async validateToken(accessToken) {
    try {
      const octokit = new Octokit({ auth: accessToken });
      await octokit.users.getAuthenticated();
      return true;
    } catch (error) {
      if (error.status === 401) {
        return false;
      }
      throw error;
    }
  }

  // Create authenticated Octokit instance
  async getAuthenticatedOctokit() {
    const tokenRecord = await this.getToken();
    
    if (!tokenRecord) {
      throw new Error('No GitHub token found. Please authenticate with GitHub first.');
    }

    // Validate token before using it
    const isValid = await this.validateToken(tokenRecord.access_token);
    if (!isValid) {
      console.warn('GitHub token is invalid or expired, removing from database');
      await this.removeInvalidToken(tokenRecord.id);
      throw new Error('GitHub token has expired. Please re-authenticate with GitHub.');
    }

    return new Octokit({
      auth: tokenRecord.access_token,
      userAgent: 'CodeNuk-GitIntegration/1.0.0',
    });
  }

  // Check repository access
  async canAccessRepository(owner, repo) {
    try {
      const octokit = await this.getAuthenticatedOctokit();
      await octokit.repos.get({ owner, repo });
      return true;
    } catch (error) {
      if (error.status === 404) {
        return false;
      }
      throw error;
    }
  }

  // Remove invalid token from database
  async removeInvalidToken(tokenId) {
    try {
      await database.query('DELETE FROM github_user_tokens WHERE id = $1', [tokenId]);
    } catch (error) {
      console.error('Error removing invalid token:', error);
    }
  }

  // Check authentication status
  async getAuthStatus() {
    const tokenRecord = await this.getToken();
    
    if (!tokenRecord) {
      return {
        connected: false,
        requires_auth: true,
        auth_url: this.getAuthUrl(Math.random().toString(36).substring(7))
      };
    }

    // Validate token by making a test API call
    try {
      const octokit = new Octokit({ auth: tokenRecord.access_token });
      await octokit.users.getAuthenticated();
      
      return {
        connected: true,
        github_username: tokenRecord.github_username,
        github_user_id: tokenRecord.github_user_id,
        scopes: tokenRecord.scopes,
        created_at: tokenRecord.created_at
      };
    } catch (error) {
      console.warn('GitHub token validation failed:', error.message);
      // Remove invalid token
      await this.removeInvalidToken(tokenRecord.id);
      return {
        connected: false,
        requires_auth: true,
        auth_url: this.getAuthUrl(Math.random().toString(36).substring(7))
      };
    }
  }

  // Revoke token
  async revokeToken() {
    const tokenRecord = await this.getToken();
    
    if (tokenRecord) {
      try {
        await fetch(`https://api.github.com/applications/${this.clientId}/grant`, {
          method: 'DELETE',
          headers: {
            'Authorization': `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')}`,
            'Accept': 'application/vnd.github.v3+json',
          },
          body: JSON.stringify({
            access_token: tokenRecord.access_token
          })
        });
      } catch (error) {
        console.error('Error revoking token on GitHub:', error);
      }

      await database.query('DELETE FROM github_user_tokens');
    }
  }
}

module.exports = GitHubOAuthService;
