import { promises as fs, accessSync } from 'fs';
import path from 'path';
import os from 'os';

export interface StorageConfig {
  mode: 'project-local' | 'user-home' | 'custom';
  customPath?: string;
  projectId?: string;
  autoSync?: boolean;
  syncInterval?: number;
}

export interface ProjectMarker {
  projectId: string;
  projectName: string;
  createdAt: string;
  atlasVersion: string;
  projectRoot: string;
}

export interface StorageLocation {
  base: string;
  data: string;
  config: string;
  exports: string;
}

export class StorageManager {
  private config: StorageConfig;
  private projectRoot: string;
  private atlasRoot: string;
  private static readonly MARKER_FILE = '.atlas-mcp';

  constructor(config?: Partial<StorageConfig>) {
    this.projectRoot = this.findProjectRoot();
    this.atlasRoot = path.dirname(path.dirname(new URL(import.meta.url).pathname));
    
    this.config = {
      mode: 'project-local',
      autoSync: true,
      syncInterval: 300000, // 5 minutes
      ...config,
    };
  }

  /**
   * Get the base storage path based on current configuration
   */
  async getStorageLocation(): Promise<StorageLocation> {
    let base: string;

    switch (this.config.mode) {
      case 'user-home':
        const projectId = this.config.projectId || await this.getProjectId();
        base = path.join(os.homedir(), '.atlas', 'projects', projectId);
        break;
      
      case 'custom':
        if (!this.config.customPath) {
          throw new Error('Custom path not specified for custom storage mode');
        }
        base = this.config.customPath;
        break;
      
      case 'project-local':
      default:
        base = path.join(this.projectRoot, '.atlas');
        break;
    }

    return {
      base,
      data: path.join(base, 'data'),
      config: path.join(base, 'config'),
      exports: path.join(base, 'exports'),
    };
  }

  /**
   * Get the user's project directory (where their code is)
   */
  getProjectRoot(): string {
    return this.projectRoot;
  }

  /**
   * Get path for storing module data
   */
  async getModuleDataPath(moduleName: string, filename: string): Promise<string> {
    const location = await this.getStorageLocation();
    return path.join(location.data, moduleName, filename);
  }

  /**
   * Get path for user-facing files (docs, configs that user should see)
   */
  getProjectFilePath(...pathSegments: string[]): string {
    return path.join(this.projectRoot, ...pathSegments);
  }

  /**
   * Ensure storage directories exist
   */
  async ensureStorageDirectories(): Promise<void> {
    const location = await this.getStorageLocation();
    
    await fs.mkdir(location.base, { recursive: true });
    await fs.mkdir(location.data, { recursive: true });
    await fs.mkdir(location.config, { recursive: true });
    await fs.mkdir(location.exports, { recursive: true });
  }

  /**
   * Save data to storage
   */
  async saveData(moduleName: string, filename: string, data: any): Promise<void> {
    const filePath = await this.getModuleDataPath(moduleName, filename);
    await fs.mkdir(path.dirname(filePath), { recursive: true });
    
    const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
    await fs.writeFile(filePath, content, 'utf-8');
  }

  /**
   * Load data from storage
   */
  async loadData(moduleName: string, filename: string): Promise<any> {
    const filePath = await this.getModuleDataPath(moduleName, filename);
    
    try {
      const content = await fs.readFile(filePath, 'utf-8');
      
      if (filename.endsWith('.json')) {
        return JSON.parse(content);
      }
      
      return content;
    } catch (error: any) {
      if (error.code === 'ENOENT') {
        return null;
      }
      throw error;
    }
  }

  /**
   * Export all data to a portable format
   */
  async exportAllData(): Promise<string> {
    const location = await this.getStorageLocation();
    const exportId = `export-${Date.now()}`;
    const exportPath = path.join(location.exports, exportId);
    
    await fs.mkdir(exportPath, { recursive: true });
    
    // Copy all data
    await this.copyDirectory(location.data, path.join(exportPath, 'data'));
    await this.copyDirectory(location.config, path.join(exportPath, 'config'));
    
    // Create metadata
    const metadata = {
      exportedAt: new Date().toISOString(),
      projectRoot: this.projectRoot,
      projectId: await this.getProjectId(),
      version: '1.0.0',
    };
    
    await fs.writeFile(
      path.join(exportPath, 'metadata.json'),
      JSON.stringify(metadata, null, 2)
    );
    
    return exportPath;
  }

  /**
   * Import data from an export
   */
  async importData(exportPath: string): Promise<void> {
    const location = await this.getStorageLocation();
    
    // Verify export structure
    const metadataPath = path.join(exportPath, 'metadata.json');
    const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
    
    // Backup current data
    const backupPath = path.join(location.base, 'backup', `backup-${Date.now()}`);
    await fs.mkdir(path.dirname(backupPath), { recursive: true });
    await this.copyDirectory(location.data, backupPath);
    
    // Import data
    await this.copyDirectory(path.join(exportPath, 'data'), location.data);
    await this.copyDirectory(path.join(exportPath, 'config'), location.config);
  }

  /**
   * Sync specific data to project directory
   */
  async syncToProject(items: Array<{ module: string; file: string; destination: string }>): Promise<void> {
    for (const item of items) {
      const sourcePath = await this.getModuleDataPath(item.module, item.file);
      const destPath = this.getProjectFilePath(item.destination);
      
      try {
        await fs.mkdir(path.dirname(destPath), { recursive: true });
        await fs.copyFile(sourcePath, destPath);
      } catch (error: any) {
        if (error.code !== 'ENOENT') {
          throw error;
        }
      }
    }
  }

  /**
   * Initialize a project with a marker file
   */
  async initializeProject(projectName?: string): Promise<ProjectMarker> {
    const markerPath = path.join(this.projectRoot, StorageManager.MARKER_FILE);
    
    // Check if already initialized
    if (await this.fileExists(markerPath)) {
      const existingMarker = await this.loadProjectMarker();
      if (existingMarker) {
        throw new Error(`Project already initialized as "${existingMarker.projectName}"`);
      }
    }
    
    const marker: ProjectMarker = {
      projectId: this.generateProjectId(),
      projectName: projectName || path.basename(this.projectRoot),
      createdAt: new Date().toISOString(),
      atlasVersion: '1.0.0',
      projectRoot: this.projectRoot,
    };
    
    await fs.writeFile(markerPath, JSON.stringify(marker, null, 2), 'utf-8');
    return marker;
  }
  
  /**
   * Load project marker from current or parent directories
   */
  async loadProjectMarker(): Promise<ProjectMarker | null> {
    const markerPath = path.join(this.projectRoot, StorageManager.MARKER_FILE);
    
    try {
      const content = await fs.readFile(markerPath, 'utf-8');
      return JSON.parse(content) as ProjectMarker;
    } catch (error) {
      return null;
    }
  }
  
  /**
   * Find the project root by looking for marker file
   */
  private findProjectRoot(): string {
    let currentDir = process.cwd();
    const root = path.parse(currentDir).root;
    
    // Search up the directory tree for .atlas-mcp marker
    while (currentDir !== root) {
      const markerPath = path.join(currentDir, StorageManager.MARKER_FILE);
      try {
        // Use sync check during initialization
        accessSync(markerPath);
        return currentDir;
      } catch {
        // Continue searching up
      }
      
      currentDir = path.dirname(currentDir);
    }
    
    // No marker found, use current directory
    return process.cwd();
  }
  
  /**
   * Get a unique project ID
   */
  private async getProjectId(): Promise<string> {
    // First try to load from marker file
    const marker = await this.loadProjectMarker();
    if (marker) {
      return marker.projectId;
    }
    
    // Otherwise generate based on path
    return this.generateProjectId();
  }
  
  /**
   * Generate a new project ID
   */
  private generateProjectId(): string {
    const projectName = path.basename(this.projectRoot);
    const pathHash = this.hashString(this.projectRoot);
    return `${projectName}-${pathHash.substring(0, 8)}`;
  }

  /**
   * Simple string hash function
   */
  private hashString(str: string): string {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash).toString(16);
  }

  /**
   * Copy directory recursively
   */
  private async copyDirectory(src: string, dest: string): Promise<void> {
    await fs.mkdir(dest, { recursive: true });
    
    const entries = await fs.readdir(src, { withFileTypes: true });
    
    for (const entry of entries) {
      const srcPath = path.join(src, entry.name);
      const destPath = path.join(dest, entry.name);
      
      if (entry.isDirectory()) {
        await this.copyDirectory(srcPath, destPath);
      } else {
        await fs.copyFile(srcPath, destPath);
      }
    }
  }

  /**
   * Check if we're running inside the Atlas MCP repository
   */
  isRunningInAtlasRepo(): boolean {
    return this.projectRoot === this.atlasRoot;
  }

  /**
   * Ensure we're not saving user data in Atlas repo
   */
  validateNotInAtlasRepo(): void {
    if (this.isRunningInAtlasRepo() && this.config.mode === 'project-local') {
      throw new Error(
        'Cannot use project-local storage mode when running from Atlas repository. ' +
        'Please run from your project directory or use user-home storage mode.'
      );
    }
  }
  
  /**
   * Check if a file exists
   */
  private async fileExists(filePath: string): Promise<boolean> {
    try {
      await fs.access(filePath);
      return true;
    } catch {
      return false;
    }
  }
}