/**
 * Authentication module for AEM Asset Upload
 * Handles JWT generation and access token management using Adobe Service Credentials
 */

const fs = require('fs');
const path = require('path');
const auth = require('@adobe/jwt-auth');

// Token cache to avoid regenerating tokens on every request
let tokenCache = {
  accessToken: null,
  expiresAt: null
};

/**
 * Reads and parses service credentials from a JSON file
 * @param {string} filePath - Path to the service credentials JSON file
 * @returns {Object} Parsed service credentials
 * @throws {Error} If file doesn't exist or is invalid JSON
 */
function readServiceCredentialsFile(filePath) {
  const absolutePath = path.isAbsolute(filePath) 
    ? filePath 
    : path.join(process.cwd(), filePath);

  if (!fs.existsSync(absolutePath)) {
    throw new Error(
      `Service credentials file not found: ${absolutePath}\n` +
      'Please download the service credentials JSON from AEM Developer Console:\n' +
      '1. Log into Adobe Cloud Manager\n' +
      '2. Open your Program > Environment\n' +
      '3. Click "..." > Developer Console\n' +
      '4. Go to Integrations > Technical Accounts\n' +
      '5. Download the service credentials JSON\n' +
      '6. Save it and update AEM_SERVICE_CREDENTIALS_FILE in your .env'
    );
  }

  try {
    const fileContent = fs.readFileSync(absolutePath, 'utf8');
    const credentials = JSON.parse(fileContent);
    
    // Validate required fields in the service credentials
    validateServiceCredentials(credentials);
    
    return credentials;
  } catch (error) {
    if (error instanceof SyntaxError) {
      throw new Error(
        `Invalid JSON in service credentials file: ${absolutePath}\n` +
        'Please ensure the file contains valid JSON downloaded from AEM Developer Console.'
      );
    }
    throw error;
  }
}

/**
 * Validates that service credentials contain all required fields
 * @param {Object} credentials - Service credentials object
 * @throws {Error} If required fields are missing
 */
function validateServiceCredentials(credentials) {
  const requiredFields = [
    'integration',
    'integration.id',
    'integration.org',
    'integration.technicalAccount',
    'integration.technicalAccount.clientId',
    'integration.technicalAccount.clientSecret',
    'integration.privateKey',
    'integration.metascopes',
    'integration.imsEndpoint'
  ];

  for (const field of requiredFields) {
    const keys = field.split('.');
    let value = credentials;
    
    for (const key of keys) {
      value = value?.[key];
    }
    
    if (!value) {
      throw new Error(
        `Invalid service credentials: missing required field '${field}'\n` +
        'Please ensure you downloaded the complete service credentials JSON from AEM Developer Console.'
      );
    }
  }
}

/**
 * Generates a JWT and exchanges it for an access token from Adobe IMS
 * @param {Object} serviceCredentials - Service credentials object from JSON file
 * @returns {Promise<string>} Access token
 * @throws {Error} If token generation fails
 */
async function generateAccessToken(serviceCredentials) {
  const integration = serviceCredentials.integration;
  
  try {
    // Use @adobe/jwt-auth to generate JWT and exchange for access token
    // This follows the pattern from Adobe's documentation
    const { access_token } = await auth({
      clientId: integration.technicalAccount.clientId,
      technicalAccountId: integration.id,
      orgId: integration.org,
      clientSecret: integration.technicalAccount.clientSecret,
      privateKey: integration.privateKey,
      metaScopes: integration.metascopes.split(','),
      ims: `https://${integration.imsEndpoint}`
    });

    return access_token;
  } catch (error) {
    throw new Error(
      `Failed to generate access token from service credentials: ${error.message}\n` +
      'Possible reasons:\n' +
      '- Service credentials have expired (they last 365 days)\n' +
      '- Invalid private key or client secret\n' +
      '- Network connectivity issues\n' +
      'Please verify your service credentials or generate new ones from AEM Developer Console.'
    );
  }
}

/**
 * Gets a valid access token, using cache if available and not expired
 * @param {string} serviceCredentialsFilePath - Path to service credentials JSON file
 * @returns {Promise<string>} Valid access token
 */
async function getAccessToken(serviceCredentialsFilePath) {
  // Check if we have a cached token that hasn't expired
  if (tokenCache.accessToken && tokenCache.expiresAt) {
    const now = Date.now();
    // Refresh token 5 minutes before expiration to be safe
    const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
    
    if (now < (tokenCache.expiresAt - bufferTime)) {
      return tokenCache.accessToken;
    }
  }

  // Generate new token
  const serviceCredentials = readServiceCredentialsFile(serviceCredentialsFilePath);
  const accessToken = await generateAccessToken(serviceCredentials);
  
  // Cache the token
  // Adobe IMS access tokens typically expire in 24 hours
  const expiresIn = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
  tokenCache = {
    accessToken,
    expiresAt: Date.now() + expiresIn
  };

  return accessToken;
}

/**
 * Clears the token cache (useful for testing or forcing token refresh)
 */
function clearTokenCache() {
  tokenCache = {
    accessToken: null,
    expiresAt: null
  };
}

/**
 * Checks if service credentials file is configured and exists
 * @returns {boolean} True if service credentials are available
 */
function hasServiceCredentials() {
  const filePath = process.env.AEM_SERVICE_CREDENTIALS_FILE;
  if (!filePath) {
    return false;
  }
  
  const absolutePath = path.isAbsolute(filePath) 
    ? filePath 
    : path.join(process.cwd(), filePath);
    
  return fs.existsSync(absolutePath);
}

module.exports = {
  getAccessToken,
  readServiceCredentialsFile,
  validateServiceCredentials,
  generateAccessToken,
  clearTokenCache,
  hasServiceCredentials
};

