import fs from 'fs/promises';
import path from 'path';
import os from 'os';
import { execSync } from 'child_process';
import { Logger } from './logger.js';

export async function downloadSchemas(logger: Logger): Promise<void> {
  const repoUrl = 'https://github.com/NocteDefensor/LudusMCP.git';
  const targetPath = 'schemas';
  const ludusConfigDir = path.join(os.homedir(), '.ludus-mcp');
  const schemasDir = path.join(ludusConfigDir, 'schemas');
  const tempDir = path.join(ludusConfigDir, 'temp-schemas-download');

  try {
    logger.info('Updating schemas from GitHub (preserving custom schemas)...');

    // Ensure .ludus-mcp directory exists
    await fs.mkdir(ludusConfigDir, { recursive: true });

    // Check if schemas directory already exists
    const schemasExists = await directoryExists(schemasDir);
    
    if (schemasExists) {
      logger.info('Existing schemas directory found - merging with official schemas');
      await mergeSchemas(schemasDir, tempDir, repoUrl, targetPath, logger);
    } else {
      logger.info('No existing schemas directory - creating fresh installation');
      await freshInstallSchemas(schemasDir, tempDir, repoUrl, targetPath, logger);
    }

    // Always update range-config.json (official file)
    await downloadRangeConfigSchema(schemasDir, logger);

    // Report final state
    const schemaFiles = await findSchemaFiles(schemasDir);
    logger.info(`Schema synchronization complete`, {
      totalFiles: schemaFiles.length,
      files: schemaFiles.map(f => path.basename(f))
    });

  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    logger.warn('Failed to update schemas (continuing without update)', { error: errorMessage });
    await removeDirectorySafe(tempDir);
  }
}

/**
 * Check if directory exists
 */
async function directoryExists(dirPath: string): Promise<boolean> {
  try {
    const stats = await fs.stat(dirPath);
    return stats.isDirectory();
  } catch {
    return false;
  }
}

/**
 * Merge official schemas with existing custom schemas
 */
async function mergeSchemas(
  schemasDir: string, 
  tempDir: string, 
  repoUrl: string, 
  targetPath: string, 
  logger: Logger
): Promise<void> {
  // Clean temp directory
  await removeDirectorySafe(tempDir);
  
  // Download official schemas to temp location
  logger.info('Downloading official schemas to temporary location...');
  await cloneSchemas(tempDir, tempDir + '-git', repoUrl, targetPath, logger);
  
  // Get list of official schema files
  const officialSchemas = await findYamlSchemaFiles(tempDir);
  const officialFileNames = officialSchemas.map(f => path.basename(f));
  
  logger.info(`Found ${officialFileNames.length} official schemas to update: ${officialFileNames.join(', ')}`);
  
  // Copy official schemas to target (overwriting official files only)
  for (const officialFile of officialSchemas) {
    const fileName = path.basename(officialFile);
    const targetFile = path.join(schemasDir, fileName);
    
    try {
      await fs.copyFile(officialFile, targetFile);
      logger.debug(`Updated official schema: ${fileName}`);
    } catch (error) {
      logger.warn(`Failed to update schema ${fileName}`, { error: error instanceof Error ? error.message : String(error) });
    }
  }
  
  // Identify custom schemas (existing files not in official list)
  const existingSchemas = await findYamlSchemaFiles(schemasDir);
  const customSchemas = existingSchemas.filter(f => 
    !officialFileNames.includes(path.basename(f))
  );
  
  if (customSchemas.length > 0) {
    const customFileNames = customSchemas.map(f => path.basename(f));
    logger.info(`Preserved ${customSchemas.length} custom schemas: ${customFileNames.join(', ')}`);
  }
  
  // Clean up temp directory
  await removeDirectorySafe(tempDir);
}

/**
 * Fresh installation of schemas (no existing directory)
 */
async function freshInstallSchemas(
  schemasDir: string, 
  tempDir: string, 
  repoUrl: string, 
  targetPath: string, 
  logger: Logger
): Promise<void> {
  // Clean temp directory
  await removeDirectorySafe(tempDir);
  
  // Direct clone to final location (original behavior for fresh installs)
  await cloneSchemas(schemasDir, tempDir, repoUrl, targetPath, logger);
}

/**
 * Find YAML schema files (.yaml and .yml) in a directory (non-recursive)
 */
async function findYamlSchemaFiles(dir: string): Promise<string[]> {
  const files: string[] = [];
  
  try {
    const items = await fs.readdir(dir, { withFileTypes: true });
    
    for (const item of items) {
      if (item.isFile() && (item.name.endsWith('.yaml') || item.name.endsWith('.yml'))) {
        files.push(path.join(dir, item.name));
      }
    }
  } catch (error) {
    // Directory doesn't exist or can't be read
  }
  
  return files.sort();
}

async function cloneSchemas(
  schemasDir: string, 
  tempDir: string, 
  repoUrl: string, 
  targetPath: string, 
  logger: Logger
): Promise<void> {
  // Clone only the specific schemas folder from main branch
  logger.info('Cloning schemas from GitHub repository (main branch)...');
  execSync(`git clone --filter=blob:none --sparse --depth 1 --branch main "${repoUrl}" "${tempDir}"`, { stdio: 'pipe' });
  execSync(`git -C "${tempDir}" sparse-checkout set "${targetPath}"`, { stdio: 'pipe' });

  const sourceSchemas = path.join(tempDir, targetPath);
  
  // Copy schemas to destination
  await copyDirectoryRecursive(sourceSchemas, schemasDir);
  
  // Clean up temp directory
  await removeDirectorySafe(tempDir);
}

async function downloadRangeConfigSchema(schemasDir: string, logger: Logger): Promise<void> {
  try {
    logger.info('Downloading range-config.json schema...');
    
    // Download range-config schema
    const response = await fetch('https://docs.ludus.cloud/schemas/range-config.json');
    if (!response.ok) {
      throw new Error(`Failed to fetch range-config schema: ${response.status} ${response.statusText}`);
    }
    
    const schemaContent = await response.text();
    const schemaPath = path.join(schemasDir, 'range-config.json');
    
    await fs.writeFile(schemaPath, schemaContent, 'utf-8');
    logger.info('Successfully downloaded range-config.json schema');
    
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    logger.warn('Failed to download range-config schema', { error: errorMessage });
  }
}

/**
 * Find all schema files (.json, .yaml, .yml) in a directory
 */
async function findSchemaFiles(dir: string): Promise<string[]> {
  const files: string[] = [];
  
  try {
    const items = await fs.readdir(dir, { withFileTypes: true });
    
    for (const item of items) {
      const fullPath = path.join(dir, item.name);
      
      if (item.isDirectory()) {
        const subFiles = await findSchemaFiles(fullPath);
        files.push(...subFiles);
      } else if (item.isFile() && (item.name.endsWith('.json') || item.name.endsWith('.yaml') || item.name.endsWith('.yml'))) {
        files.push(fullPath);
      }
    }
  } catch (error) {
    // Directory doesn't exist or can't be read
  }
  
  return files.sort();
}

/**
 * Recursively copy directory contents
 */
async function copyDirectoryRecursive(source: string, destination: string): Promise<void> {
  // Ensure destination directory exists
  await fs.mkdir(destination, { recursive: true });

  const items = await fs.readdir(source, { withFileTypes: true });

  for (const item of items) {
    const sourcePath = path.join(source, item.name);
    const destPath = path.join(destination, item.name);

    if (item.isDirectory()) {
      await copyDirectoryRecursive(sourcePath, destPath);
    } else if (item.isFile()) {
      await fs.copyFile(sourcePath, destPath);
    }
  }
}

/**
 * Windows-safe directory removal
 */
async function removeDirectorySafe(dirPath: string): Promise<void> {
  try {
    await fs.access(dirPath);
    // Directory exists, try to remove it
    await fs.rm(dirPath, { recursive: true, force: true });
  } catch (error) {
    // Directory doesn't exist or couldn't be removed - that's fine for cleanup
  }
} 