/**
 * Example: Using DirectBinaryUpload class
 * 
 * This example demonstrates fine-grained control over the upload process.
 * The DirectBinaryUpload class is ideal for:
 * - Uploading from streams or buffers
 * - Uploading from remote URLs
 * - Custom file handling logic
 * - More control over individual file uploads
 * - Uploading generated or processed content
 * 
 * IMPORTANT: DirectBinaryUpload does NOT automatically create folders in AEM.
 * For uploading nested folder structures from the filesystem, use FileSystemUpload instead.
 * 
 * This example demonstrates uploading from a remote URL (https://placehold.co).
 */

const { DirectBinaryUpload, DirectBinaryUploadOptions, DirectBinaryUploadErrorCodes } = require('@adobe/aem-upload');
const https = require('https');
const path = require('path');
const { validateConfig, getUploadOptions, getTargetFolder, getRemoteFileUrls } = require('../src/config');
const { 
  logSuccess, 
  logError, 
  logInfo,
  logWarning, 
  createSpinner,
  formatBytes,
  formatTime,
  getAemAssetsUrl
} = require('../src/utils');
const { logException } = require('../src/errorHandler');

/**
 * Fetches a remote file as a buffer
 * @param {string} url - URL of the remote file
 * @returns {Promise<Buffer>} File buffer
 */
function fetchRemoteFile(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (response) => {
      if (response.statusCode !== 200) {
        reject(new Error(`Failed to fetch ${url}: ${response.statusCode}`));
        return;
      }
      
      const chunks = [];
      response.on('data', (chunk) => chunks.push(chunk));
      response.on('end', () => resolve(Buffer.concat(chunks)));
      response.on('error', reject);
    }).on('error', reject);
  });
}

/**
 * Creates upload file objects for DirectBinaryUpload from remote URLs
 * @param {Array<Object>} remoteFiles - Array of objects with url, fileName, targetFolder
 * @returns {Array<Object>} Array of upload file objects
 */
async function createUploadFilesFromUrls(remoteFiles) {
  const uploadFiles = [];
  
  for (const remoteFile of remoteFiles) {
    logInfo(`Fetching: ${remoteFile.fileName} from ${remoteFile.url}`);
    try {
      const fileBuffer = await fetchRemoteFile(remoteFile.url);
      uploadFiles.push({
        fileName: remoteFile.fileName,
        fileSize: fileBuffer.length,
        blob: fileBuffer,  // DirectBinaryUpload uses 'blob' for buffers
        targetFolder: remoteFile.targetFolder,
        targetFile: `${remoteFile.targetFolder}/${remoteFile.fileName}`,
        sourceUrl: remoteFile.url  // Track source URL for display in summary
      });
      logSuccess(`Downloaded: ${remoteFile.fileName} (${formatBytes(fileBuffer.length)})`);
    } catch (error) {
      logError(`Failed to fetch ${remoteFile.fileName}: ${error.message}`);
    }
  }
  
  return uploadFiles;
}

/**
 * Main function to demonstrate DirectBinaryUpload
 */
async function main() {
  try {
    // Validate configuration (including remote URLs requirement)
    validateConfig({ requireRemoteUrls: true });
    logSuccess('Configuration validated successfully');

    // Get upload options (now async to support service credentials)
    const options = await getUploadOptions();
    const targetFolder = getTargetFolder();
    const remoteUrls = getRemoteFileUrls();

    logInfo(`AEM URL: ${options.url}`);
    logInfo(`Target folder: ${targetFolder}`);

    // Build remote files array from environment configuration
    // This demonstrates DirectBinaryUpload's ability to upload from remote sources
    // Configure URLs via .env file (REMOTE_FILE_URL_1, REMOTE_FILE_URL_2, etc.)
    const remoteFiles = remoteUrls.map((url, index) => {
      // Extract filename from URL or generate one
      let fileName;
      try {
        const urlPath = new URL(url).pathname;
        const fileNameFromUrl = path.basename(urlPath);
        // If URL has a valid filename with extension, use it, otherwise generate one
        fileName = fileNameFromUrl && fileNameFromUrl.includes('.') 
          ? fileNameFromUrl 
          : `remote-file-${index + 1}.png`;
      } catch {
        // If URL parsing fails, generate a generic filename
        fileName = `remote-file-${index + 1}.png`;
      }

      return {
        url,
        fileName,
        targetFolder: targetFolder
      };
    });

    logInfo(`Uploading ${remoteFiles.length} remote file(s) from URLs`);
    logInfo(`Source: URLs configured in .env file`);
    
    // Display all URLs being uploaded
    console.log('');
    remoteFiles.forEach((file, index) => {
      console.log(`  ${index + 1}. ${file.url}`);
    });

    console.log('\n--- Starting Upload ---\n');

    const startTime = Date.now();
    const spinner = createSpinner('Downloading remote files...');

    // Fetch remote files and create upload objects
    const uploadFiles = await createUploadFilesFromUrls(remoteFiles);

    if (uploadFiles.length === 0) {
      spinner.stop();
      logError('No files downloaded successfully');
      process.exit(1);
    }

    spinner.stop();
    console.log('');

    // Initialize DirectBinaryUpload
    const upload = new DirectBinaryUpload();

    // Perform the upload with progress tracking
    const results = [];
    
    // Track progress - attach listener to upload instance once
    upload.on('fileprogress', (data) => {
      const percentage = ((data.transferred / data.fileSize) * 100).toFixed(1);
      process.stdout.write(
        `\r  Progress: ${percentage}% - ${formatBytes(data.transferred)}/${formatBytes(data.fileSize)}`
      );
    });
    
    for (let i = 0; i < uploadFiles.length; i++) {
      const uploadFile = uploadFiles[i];
      const fileStartTime = Date.now();
      
      logInfo(`[${i + 1}/${uploadFiles.length}] Uploading: ${uploadFile.fileName} (${formatBytes(uploadFile.fileSize)})`);

      try {
        // Full target URL for this specific file (includes folder structure)
        const fullUrl = `${options.url}${uploadFile.targetFolder}`;
        
        // Upload options for each file
        const uploadOptions = new DirectBinaryUploadOptions()
          .withUrl(fullUrl)
          .withUploadFiles([uploadFile]);
        
        // Add HTTP options (auth is already in headers from config)
        uploadOptions
          .withHttpOptions({
            headers: {
              ...options.headers,
              'X-Upload-Source': 'DirectBinaryUpload-Example'
            }
          })
          .withMaxConcurrent(5);

        // Upload individual file and wait for completion
        const uploadResult = await upload.uploadFiles(uploadOptions);
        const fileEndTime = Date.now();
        const elapsedTime = fileEndTime - fileStartTime;

        process.stdout.write('\n');
        
        // Extract the result for this file - check for errors properly
        const detailedResults = uploadResult.detailedResult || [];
        const fileResult = detailedResults[0] || {};
        
        // Check for errors at multiple levels (as per library documentation)
        const topLevelErrors = uploadResult.errors || [];
        const fileErrors = fileResult.result?.errors || [];
        const allErrors = [...topLevelErrors, ...fileErrors];
        
        if (allErrors.length > 0) {
          // File failed - check if it's a folder-not-found error
          const notFoundError = allErrors.find(err => err.code === DirectBinaryUploadErrorCodes.NOT_FOUND);
          
          if (notFoundError) {
            logError(`Failed: ${uploadFile.fileName} - Folder not found`);
          } else {
            const errorMsg = allErrors[0].message || 'Unknown error';
            logError(`Failed: ${uploadFile.fileName} - ${errorMsg}`);
          }
          
          results.push({ 
            ...fileResult, 
            success: false,
            fileName: uploadFile.fileName,
            targetFile: uploadFile.targetFile,
            targetFolder: uploadFile.targetFolder,
            sourceUrl: uploadFile.sourceUrl,
            errors: allErrors
          });
        } else {
          logSuccess(`Completed: ${uploadFile.fileName} (${formatTime(elapsedTime)})`);
          results.push({ 
            ...fileResult, 
            success: true, 
            elapsedTime,
            fileName: uploadFile.fileName,
            targetFile: uploadFile.targetFile,
            targetFolder: uploadFile.targetFolder,
            sourceUrl: uploadFile.sourceUrl
          });
        }

      } catch (error) {
        logError(`Error uploading ${uploadFile.fileName}: ${error.message}`);
        results.push({ 
          fileName: uploadFile.fileName,
          targetFile: uploadFile.targetFile,
          targetFolder: uploadFile.targetFolder,
          sourceUrl: uploadFile.sourceUrl,
          error, 
          success: false 
        });
      }

      console.log('');
    }

    const totalTime = Date.now() - startTime;

    // Display summary
    const chalk = require('chalk');
    console.log('\n' + chalk.bold('Upload Summary:'));
    console.log(chalk.gray('─'.repeat(50)));
    
    const successful = results.filter(r => r.success);
    const failed = results.filter(r => !r.success);
    
    console.log(`Total files: ${chalk.cyan(results.length)}`);
    console.log(`Successful: ${chalk.green(successful.length)}`);
    if (failed.length > 0) {
      console.log(`Failed: ${chalk.red(failed.length)}`);
    }
    console.log(`Total time: ${chalk.yellow(formatTime(totalTime))}`);
    console.log(chalk.gray('─'.repeat(50)));

    // Display results with clickable folder URL
    if (successful.length > 0) {
      const folderUrl = getAemAssetsUrl(options.url, targetFolder);
      console.log(`\n✅ Successfully uploaded to AEM: ${folderUrl}`);
      successful.forEach(r => {
        // Show full path relative to target folder (includes subfolder)
        const relativePath = r.targetFile.replace(`${targetFolder}/`, '');
        console.log(`  → ${relativePath}`);
        console.log(`     Source: ${r.sourceUrl}`);
      });
    }
    
    if (failed.length > 0) {
      const folderErrors = failed.filter(r => r.errors?.some(e => e.code === DirectBinaryUploadErrorCodes.NOT_FOUND));
      const otherErrors = failed.filter(r => !r.errors?.some(e => e.code === DirectBinaryUploadErrorCodes.NOT_FOUND));
      
      if (folderErrors.length > 0) {
        console.log(`\n❌ Failed (folder not found in AEM):`);
        folderErrors.forEach(r => {
          const relativePath = r.targetFile.replace(`${targetFolder}/`, '');
          console.log(`  → ${relativePath}`);
          console.log(`     Source: ${r.sourceUrl}`);
        });
        console.log('');
        logInfo('💡 Tip: Use FileSystemUpload (option 1) to auto-create folders');
      }
      
      if (otherErrors.length > 0) {
        console.log(`\n❌ Failed (other errors):`);
        otherErrors.forEach(r => {
          const relativePath = r.targetFile.replace(`${targetFolder}/`, '');
          const errorMsg = r.errors?.[0]?.message || 'Unknown error';
          console.log(`  → ${relativePath}: ${errorMsg}`);
          console.log(`     Source: ${r.sourceUrl}`);
        });
      }
      
      console.log('');
      process.exit(1);
    } else {
      logSuccess('\nAll files uploaded successfully!');
      console.log('');
      process.exit(0);
    }

  } catch (error) {
    logException(error, 'Upload failed');
    process.exit(1);
  }
}

// Run the example
if (require.main === module) {
  console.log('\n=== DirectBinaryUpload Example ===\n');
  main();
}

module.exports = { main };

