/**
 * Backup Data Importer (Restore)
 *
 * Restores company data from an extracted backup directory.
 * Reads data.json files for each model within each category and upserts
 * documents into MongoDB. Copies binary files back to the uploads/ directory.
 *
 * Supports multi-model categories where a single category contains data from
 * multiple related models stored in separate subdirectories.
 */

import fs from 'fs';
import path from 'path';
import { getModels } from '../../models';
import { BACKUP_CATEGORIES, getCategoryModels, type BackupCategory, type BackupModelEntry } from './categoryMap';

export interface ImportStats {
  categoriesRestored: number;
  recordsRestored: number;
  filesRestored: number;
  errors: string[];
}

export interface ImportProgress {
  step: string;
  progress: number;
}

/**
 * Import (restore) company data from an extracted backup directory.
 * Uses upsert logic: existing documents are updated, new ones are created.
 */
export async function importCompanyData(
  companyId: string,
  sourceDir: string,
  categories: string[],
  onProgress?: (progress: ImportProgress) => void,
): Promise<ImportStats> {
  const models = getModels();
  const stats: ImportStats = {
    categoriesRestored: 0,
    recordsRestored: 0,
    filesRestored: 0,
    errors: [],
  };

  // Read manifest
  const manifestPath = path.join(sourceDir, 'manifest.json');
  if (!fs.existsSync(manifestPath)) {
    throw new Error('Invalid backup: manifest.json not found');
  }

  let manifest: any;
  try {
    manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
  } catch {
    throw new Error('Invalid backup: manifest.json is corrupted');
  }

  // Verify backup version compatibility
  if (manifest.version && manifest.version !== '1.0') {
    console.warn(`[BackupImporter] Backup version ${manifest.version} may not be fully compatible`);
  }

  const totalCategories = categories.length;

  for (let i = 0; i < categories.length; i++) {
    const categoryId = categories[i];
    const category = BACKUP_CATEGORIES[categoryId];

    if (!category) {
      stats.errors.push(`Unknown category: ${categoryId}`);
      continue;
    }

    const progressPercent = Math.round(((i + 1) / totalCategories) * 80); // 0–80% for data import
    onProgress?.({
      step: `Restoring ${category.label}...`,
      progress: progressPercent,
    });

    const categoryDir = path.join(sourceDir, category.directory);
    if (!fs.existsSync(categoryDir)) {
      // Category not present in this backup, skip
      continue;
    }

    // Get all model entries for this category (primary + sub-models)
    const modelEntries = getCategoryModels(category);
    let categoryHadData = false;

    for (const entry of modelEntries) {
      const model = models[entry.modelName as keyof typeof models] as any;

      if (!model) {
        stats.errors.push(`Model not found: ${entry.modelName}`);
        continue;
      }

      const subDir = entry.subdirectory || 'data';
      const modelDir = path.join(categoryDir, subDir);
      const dataFilePath = path.join(modelDir, 'data.json');

      if (!fs.existsSync(dataFilePath)) {
        // Sub-model data not present, skip
        continue;
      }

      try {
        const documents = JSON.parse(fs.readFileSync(dataFilePath, 'utf-8'));

        // Collect exclude fields from category and entry
        const allExcludeFields = new Set([...(category.excludeFields || []), ...(entry.excludeFields || [])]);

        for (const doc of documents) {
          try {
            // Set the company ID to the target company
            const restoredDoc = {
              ...doc,
              companyId,
              // Use the original createdAt/updatedAt if present
              createdAt: doc.createdAt ? new Date(doc.createdAt) : new Date(),
              updatedAt: new Date(),
            };

            // Remove fields that shouldn't be set
            delete restoredDoc._id;
            delete restoredDoc.__v;

            // Remove excluded fields
            for (const field of allExcludeFields) {
              delete restoredDoc[field];
            }

            // Try to find existing document by a unique combination and upsert
            if (doc.id) {
              // Use findOneAndUpdate with upsert for id-based matching
              await model.findOneAndUpdate(
                { id: doc.id, companyId },
                { $set: restoredDoc },
                { upsert: true, setDefaultsOnInsert: true },
              );
            } else {
              // No id, just create
              await model.create(restoredDoc);
            }

            stats.recordsRestored++;
            categoryHadData = true;
          } catch (docErr: any) {
            stats.errors.push(`Error restoring ${categoryId}/${entry.modelName} record: ${docErr.message}`);
          }
        }
      } catch (err: any) {
        stats.errors.push(`Error processing ${categoryId}/${entry.modelName}: ${err.message}`);
      }

      // Copy associated files back for this sub-model
      const assetsDir = path.join(modelDir, 'assets');
      if (fs.existsSync(assetsDir)) {
        restoreAssetFiles(assetsDir, entry, stats);
      }
    }

    // Also copy files from the top-level assets directory (for legacy backups)
    const topLevelAssetsDir = path.join(categoryDir, 'assets');
    if (fs.existsSync(topLevelAssetsDir)) {
      restoreAssetFiles(topLevelAssetsDir, { modelName: category.modelName, subdirectory: 'data' }, stats);
    }

    if (categoryHadData) {
      stats.categoriesRestored++;
    }
  }

  // Restore uploaded files from the _uploads directory
  const uploadsSourceDir = path.join(sourceDir, '_uploads');
  if (fs.existsSync(uploadsSourceDir)) {
    onProgress?.({
      step: 'Restoring uploaded files...',
      progress: 85,
    });

    restoreUploadsDirectory(uploadsSourceDir, stats);
  }

  return stats;
}

/**
 * Restore asset files from a backup directory to the uploads/ directory.
 */
function restoreAssetFiles(
  assetsDir: string,
  entry: BackupModelEntry,
  stats: ImportStats,
): void {
  const uploadsBase = path.join(process.cwd(), 'uploads');

  // Walk the assets directory recursively, preserving the subdirectory structure.
  // The relativeTo parameter tracks the path relative to the assetsDir root.
  function walkAndCopy(dir: string, relativePath: string) {
    const entries = fs.readdirSync(dir, { withFileTypes: true });

    for (const entry of entries) {
      const srcPath = path.join(dir, entry.name);
      const entryRelPath = relativePath ? path.join(relativePath, entry.name) : entry.name;

      if (entry.isDirectory()) {
        walkAndCopy(srcPath, entryRelPath);
      } else if (entry.isFile()) {
        // Preserve the directory structure under uploads/
        // e.g., assets/brand-assets/logo.png → uploads/brand-assets/logo.png
        const destDir = path.join(uploadsBase, path.dirname(entryRelPath));
        const destPath = path.join(destDir, entry.name);

        try {
          fs.mkdirSync(destDir, { recursive: true });
          fs.copyFileSync(srcPath, destPath);
          stats.filesRestored++;
        } catch (copyErr: any) {
          stats.errors.push(`Error copying file ${entry.name}: ${copyErr.message}`);
        }
      }
    }
  }

  walkAndCopy(assetsDir, '');
}

/**
 * Determine the upload subdirectory for a given model name.
 */
function determineSubdirectory(modelName: string): string {
  const mapping: Record<string, string> = {
    BrandAsset: 'brand-assets',
    Stationery: 'stationery',
    StationeryTemplate: 'stationery',
    Testimonial: 'testimonials',
    LandingPageContentOS: 'landing-pages',
    LandingPageDeployment: 'landing-pages',
    SocialMediaPublication: 'social-media',
    Presentation: 'presentations',
    SalesCollateral: 'sales-collateral',
    ImageGeneration: 'images',
    LegalDocument: 'documents',
    AdCreativeAsset: 'ad-creatives',
    HRAsset: 'hr-assets',
    Book: 'books',
    BookChapter: 'books',
    Course: 'courses',
    CourseLesson: 'courses',
    Event: 'events',
  };
  return mapping[modelName] || '';
}

/**
 * Restore the _uploads directory from backup to the real uploads/ directory.
 */
function restoreUploadsDirectory(uploadsSourceDir: string, stats: ImportStats): void {
  const uploadsBase = path.join(process.cwd(), 'uploads');
  fs.mkdirSync(uploadsBase, { recursive: true });

  function walkAndCopy(srcDir: string, destBase: string) {
    fs.mkdirSync(destBase, { recursive: true });
    const entries = fs.readdirSync(srcDir, { withFileTypes: true });

    for (const entry of entries) {
      const srcPath = path.join(srcDir, entry.name);
      const destPath = path.join(destBase, entry.name);

      if (entry.isDirectory()) {
        walkAndCopy(srcPath, destPath);
      } else if (entry.isFile()) {
        try {
          fs.copyFileSync(srcPath, destPath);
          stats.filesRestored++;
        } catch (copyErr: any) {
          stats.errors.push(`Error restoring file ${entry.name}: ${copyErr.message}`);
        }
      }
    }
  }

  walkAndCopy(uploadsSourceDir, uploadsBase);
}