import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { verifyAdminAuth } from '@/lib/auth';

// POST - Upload and deploy update.zip
export async function POST(req: NextRequest) {
  const authResult = await verifyAdminAuth(req);
  if ('error' in authResult) {
    return authResult.error;
  }

  // Only super admin can deploy updates
  if (authResult.session.role !== 'super_admin') {
    return NextResponse.json({ error: 'Only super admin can deploy updates' }, { status: 403 });
  }

  try {
    const formData = await req.formData();
    const file = formData.get('file') as File | null;

    if (!file) {
      return NextResponse.json({ success: false, error: 'No file uploaded' }, { status: 400 });
    }

    // Validate file type
    if (!file.name.endsWith('.zip')) {
      return NextResponse.json({ success: false, error: 'Only .zip files are accepted' }, { status: 400 });
    }

    // Validate file size (max 500MB)
    const maxSize = 500 * 1024 * 1024;
    if (file.size > maxSize) {
      return NextResponse.json({ success: false, error: 'File too large. Maximum size is 500MB.' }, { status: 400 });
    }

    const projectRoot = process.cwd();

    // Save the uploaded file temporarily
    const updateZipPath = path.join(projectRoot, 'update.zip');
    const buffer = Buffer.from(await file.arrayBuffer());
    fs.writeFileSync(updateZipPath, buffer);

    // Create a backup of critical files
    const backupDir = path.join(projectRoot, '.backup', Date.now().toString());
    fs.mkdirSync(backupDir, { recursive: true });

    // Backup .env if it exists
    const envPath = path.join(projectRoot, '.env');
    if (fs.existsSync(envPath)) {
      fs.copyFileSync(envPath, path.join(backupDir, '.env'));
    }

    // Backup database
    const dbPaths = [
      path.join(projectRoot, 'db', 'dev.db'),
      path.join(projectRoot, 'prisma', 'dev.db'),
    ];
    for (const dbP of dbPaths) {
      if (fs.existsSync(dbP)) {
        const dbBackupDir = path.join(backupDir, path.dirname(dbP).replace(projectRoot, ''));
        fs.mkdirSync(dbBackupDir, { recursive: true });
        fs.copyFileSync(dbP, path.join(dbBackupDir, path.basename(dbP)));
      }
    }

    // Extract the ZIP to a temporary directory
    const extractDir = path.join(projectRoot, '.update-temp');
    if (fs.existsSync(extractDir)) {
      fs.rmSync(extractDir, { recursive: true, force: true });
    }
    fs.mkdirSync(extractDir, { recursive: true });

    // Extract using unzip command
    try {
      execSync(`unzip -o -q "${updateZipPath}" -d "${extractDir}"`, { encoding: 'utf-8', timeout: 120000 });
    } catch (unzipError: unknown) {
      const message = unzipError instanceof Error ? unzipError.message : 'Extraction failed';
      // Clean up
      fs.rmSync(extractDir, { recursive: true, force: true });
      fs.unlinkSync(updateZipPath);
      return NextResponse.json({
        success: false,
        error: `Failed to extract ZIP: ${message}`,
      }, { status: 500 });
    }

    // Determine the source directory (might be nested in a folder)
    let sourceDir = extractDir;
    const extractEntries = fs.readdirSync(extractDir);
    // If there's only one directory in the extracted content, use that
    if (extractEntries.length === 1 && fs.statSync(path.join(extractDir, extractEntries[0])).isDirectory()) {
      sourceDir = path.join(extractDir, extractEntries[0]);
    }

    // List of files/folders to NEVER copy (auto-protected)
    const PROTECTED_ITEMS = ['.env', '.git', 'node_modules', 'storage', '.env.local', '.env.production'];

    // Files/folders to copy from the update
    const INCLUDE_PATTERNS = [
      'app', 'components', 'lib', 'hooks', 'styles', 'prisma', 'public',
      'src', 'scripts', 'mini-services',
      'next.config.ts', 'next.config.js', 'next.config.mjs',
      'package.json', 'package-lock.json', 'bun.lockb',
      'tailwind.config.ts', 'tailwind.config.js',
      'tsconfig.json', 'postcss.config.js', 'postcss.config.mjs',
      'components.json', 'middleware.ts',
    ];

    let copiedFiles = 0;
    const skippedItems: string[] = [];

    // Copy files from extracted update to project
    const updateEntries = fs.readdirSync(sourceDir);

    for (const entry of updateEntries) {
      // Skip protected items
      if (PROTECTED_ITEMS.includes(entry)) {
        skippedItems.push(entry);
        continue;
      }

      // Only copy whitelisted items if they exist in the update
      const srcPath = path.join(sourceDir, entry);
      const destPath = path.join(projectRoot, entry);

      try {
        if (fs.statSync(srcPath).isDirectory()) {
          // For directories, merge/copy
          if (entry === 'src' || INCLUDE_PATTERNS.includes(entry)) {
            // Copy entire directory
            if (fs.existsSync(destPath)) {
              fs.rmSync(destPath, { recursive: true, force: true });
            }
            copyDirRecursive(srcPath, destPath);
            copiedFiles++;
          }
        } else {
          // For files, copy if in include list
          if (INCLUDE_PATTERNS.includes(entry)) {
            fs.copyFileSync(srcPath, destPath);
            copiedFiles++;
          }
        }
      } catch (copyError: unknown) {
        const errMsg = copyError instanceof Error ? copyError.message : 'Copy failed';
        console.error(`Failed to copy ${entry}:`, errMsg);
      }
    }

    // Install dependencies if package.json was updated
    let depsInstalled = false;
    const newPkgPath = path.join(projectRoot, 'package.json');
    const pkgChanged = fs.existsSync(newPkgPath);
    if (pkgChanged) {
      try {
        execSync('bun install', { cwd: projectRoot, encoding: 'utf-8', timeout: 180000 });
        depsInstalled = true;
      } catch {
        // Try npm as fallback
        try {
          execSync('npm install', { cwd: projectRoot, encoding: 'utf-8', timeout: 180000 });
          depsInstalled = true;
        } catch { /* ignore */ }
      }
    }

    // Run prisma generate if prisma schema changed
    let prismaGenerated = false;
    const schemaPath = path.join(projectRoot, 'prisma', 'schema.prisma');
    if (fs.existsSync(schemaPath)) {
      try {
        execSync('npx prisma generate', { cwd: projectRoot, encoding: 'utf-8', timeout: 60000 });
        prismaGenerated = true;
      } catch { /* ignore */ }
    }

    // Run prisma db push if prisma schema changed
    let dbPushed = false;
    if (fs.existsSync(schemaPath)) {
      try {
        execSync('npx prisma db push --accept-data-loss', { cwd: projectRoot, encoding: 'utf-8', timeout: 60000 });
        dbPushed = true;
      } catch { /* ignore */ }
    }

    // Clean up
    fs.rmSync(extractDir, { recursive: true, force: true });
    // Keep update.zip for reference but rename it
    try {
      const archiveName = `update-${Date.now()}.zip`;
      fs.renameSync(updateZipPath, path.join(projectRoot, '.backup', archiveName));
    } catch { /* ignore */ }

    return NextResponse.json({
      success: true,
      message: 'Update deployed successfully!',
      details: {
        filesUpdated: copiedFiles,
        protectedItemsSkipped: skippedItems,
        dependenciesInstalled: depsInstalled,
        prismaGenerated,
        databaseMigrated: dbPushed,
        backupLocation: backupDir,
        note: 'The application will use the updated files on the next request. Some changes may require a server restart.',
      },
    });
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : 'Unknown error';
    return NextResponse.json({
      success: false,
      error: `Update failed: ${message}`,
    }, { status: 500 });
  }
}

// GET - Check update status and get instructions
export async function GET(req: NextRequest) {
  const authResult = await verifyAdminAuth(req);
  if ('error' in authResult) {
    return authResult.error;
  }

  const projectRoot = process.cwd();

  // Get project info
  let projectName = 'Needyfy';
  let projectVersion = '1.0.0';
  try {
    const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf-8'));
    projectName = pkg.name || 'Needyfy';
    projectVersion = pkg.version || '1.0.0';
  } catch { /* ignore */ }

  return NextResponse.json({
    success: true,
    info: {
      projectName,
      currentVersion: projectVersion,
      updateInstructions: {
        steps: [
          'Open your terminal in the project directory.',
          'Run: bun run build (if applicable)',
          'Select and ZIP the following folders/files from your project root: src/, prisma/, public/, package.json, tailwind.config.ts, tsconfig.json, next.config.ts, components.json',
          'Name the archive update.zip.',
          'Upload the ZIP file using the form below.',
        ],
        neverInclude: [
          { path: '.env', reason: 'Contains database passwords (auto-protected)' },
          { path: 'storage/', reason: 'Contains uploaded files, logs (auto-protected)' },
          { path: '.git/', reason: 'Source control history (auto-protected)' },
          { path: 'node_modules/', reason: 'Dev dependencies (auto-protected)' },
        ],
        safetyNote: 'Even if your ZIP accidentally contains .env, storage/, .git/, or node_modules/, the updater will automatically skip them. Your database credentials and uploaded files are always protected.',
      },
    },
  });
}

// Helper: Copy directory recursively
function copyDirRecursive(src: string, dest: string) {
  fs.mkdirSync(dest, { recursive: true });
  const entries = fs.readdirSync(src, { withFileTypes: true });

  for (const entry of entries) {
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);

    // Skip protected items
    if (['.env', '.git', 'node_modules', 'storage'].includes(entry.name)) {
      continue;
    }

    if (entry.isDirectory()) {
      copyDirRecursive(srcPath, destPath);
    } else {
      fs.copyFileSync(srcPath, destPath);
    }
  }
}
