// services/file-storage.service.js
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const database = require('../config/database');

class FileStorageService {
  constructor() {
    this.supportedTextExtensions = new Set([
      '.js', '.ts', '.jsx', '.tsx', '.vue', '.py', '.java', '.cpp', '.c', '.cs',
      '.php', '.rb', '.go', '.rs', '.kt', '.swift', '.scala', '.clj', '.hs',
      '.elm', '.ml', '.fs', '.vb', '.pas', '.asm', '.sql', '.sh', '.bash',
      '.ps1', '.bat', '.cmd', '.html', '.htm', '.xml', '.css', '.scss', '.sass',
      '.less', '.json', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.conf',
      '.env', '.md', '.txt', '.rst', '.adoc', '.tex', '.r', '.m', '.pl',
      '.lua', '.dart', '.jl', '.nim', '.zig', '.v', '.d', '.cr', '.ex', '.exs'
    ]);

    this.languageMap = {
      '.js': 'javascript', '.ts': 'typescript', '.jsx': 'javascript', '.tsx': 'typescript',
      '.vue': 'vue', '.py': 'python', '.java': 'java', '.cpp': 'cpp', '.c': 'c',
      '.cs': 'csharp', '.php': 'php', '.rb': 'ruby', '.go': 'go', '.rs': 'rust',
      '.kt': 'kotlin', '.swift': 'swift', '.scala': 'scala', '.clj': 'clojure',
      '.hs': 'haskell', '.elm': 'elm', '.ml': 'ocaml', '.fs': 'fsharp',
      '.vb': 'vbnet', '.html': 'html', '.css': 'css', '.scss': 'scss',
      '.json': 'json', '.yaml': 'yaml', '.yml': 'yaml', '.xml': 'xml',
      '.sql': 'sql', '.sh': 'bash', '.md': 'markdown'
    };
  }

  // Initialize storage record for a repository
  async initializeRepositoryStorage(repositoryId, localPath) {
    const query = `
      INSERT INTO repository_storage (
        repository_id, local_path, storage_status, download_started_at
      ) VALUES ($1, $2, $3, NOW())
      ON CONFLICT (repository_id) 
      DO UPDATE SET 
        local_path = $2,
        storage_status = $3,
        download_started_at = NOW(),
        updated_at = NOW()
      RETURNING *
    `;
    
    const result = await database.query(query, [repositoryId, localPath, 'downloading']);
    return result.rows[0];
  }

  // Process and store directory structure
  async processDirectoryStructure(storageId, repositoryId, basePath, currentPath = '', parentDirId = null, level = 0) {
    const fullPath = path.join(basePath, currentPath);
    
    if (!fs.existsSync(fullPath)) {
      return null;
    }

    const stats = fs.statSync(fullPath);
    
    if (!stats.isDirectory()) {
      return null;
    }

    // Insert directory record
    const dirName = currentPath === '' ? '.' : path.basename(currentPath);
    const relativePath = currentPath === '' ? '' : currentPath;
    
    const dirQuery = `
      INSERT INTO repository_directories (
        repository_id, storage_id, parent_directory_id, directory_name,
        relative_path, absolute_path, level
      ) VALUES ($1, $2, $3, $4, $5, $6, $7)
      RETURNING *
    `;
    
    const dirResult = await database.query(dirQuery, [
      repositoryId, storageId, parentDirId, dirName,
      relativePath, fullPath, level
    ]);
    
    const directoryRecord = dirResult.rows[0];
    let totalFiles = 0;
    let totalSubDirs = 0;
    let totalSize = 0;

    try {
      const items = fs.readdirSync(fullPath);
      
      for (const item of items) {
        const itemPath = path.join(fullPath, item);
        const itemRelativePath = currentPath ? path.join(currentPath, item) : item;
        const itemStats = fs.statSync(itemPath);
        
        if (itemStats.isDirectory()) {
          // Recursively process subdirectory
          const subDir = await this.processDirectoryStructure(
            storageId, repositoryId, basePath, itemRelativePath, 
            directoryRecord.id, level + 1
          );
          if (subDir) {
            totalSubDirs++;
          }
        } else if (itemStats.isFile()) {
          // Process file
          const fileRecord = await this.processFile(
            storageId, repositoryId, directoryRecord.id, itemPath, itemRelativePath
          );
          if (fileRecord) {
            totalFiles++;
            // IMPORTANT: node-postgres returns BIGINT columns as strings.
            // Avoid string concatenation causing huge numeric strings by summing the
            // actual filesystem-reported size (a number) for aggregation.
            totalSize += Number(itemStats.size) || 0;
          }
        }
      }
    } catch (error) {
      console.warn(`Error processing directory ${fullPath}:`, error.message);
    }

    // Update directory stats
    await database.query(`
      UPDATE repository_directories 
      SET files_count = $1, subdirectories_count = $2, total_size_bytes = $3, updated_at = NOW()
      WHERE id = $4
    `, [totalFiles, totalSubDirs, totalSize, directoryRecord.id]);

    return directoryRecord;
  }

  // Process and store individual file
  async processFile(storageId, repositoryId, directoryId, absolutePath, relativePath) {
    try {
      const stats = fs.statSync(absolutePath);
      const filename = path.basename(absolutePath);
      const extension = path.extname(filename).toLowerCase();
      
      // Calculate file hash
      const fileBuffer = fs.readFileSync(absolutePath);
      const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
      
      // Determine if file is binary
      const isBinary = !this.supportedTextExtensions.has(extension) || this.isBinaryContent(fileBuffer);
      
      // Get MIME type (simplified)
      const mimeType = this.getMimeType(extension, isBinary);
      
      // Insert file record
      const fileQuery = `
        INSERT INTO repository_files (
          repository_id, storage_id, directory_id, filename, file_extension,
          relative_path, absolute_path, file_size_bytes, file_hash,
          mime_type, is_binary, encoding
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
        RETURNING *
      `;
      
      const fileResult = await database.query(fileQuery, [
        repositoryId, storageId, directoryId, filename, extension,
        relativePath, absolutePath, stats.size, hash,
        mimeType, isBinary, isBinary ? null : 'utf-8'
      ]);
      
      const fileRecord = fileResult.rows[0];
      
      // Process file content if it's a text file and not too large (< 10MB)
      if (!isBinary && stats.size < 10 * 1024 * 1024) {
        await this.processFileContent(fileRecord.id, absolutePath, extension);
      }
      
      return fileRecord;
      
    } catch (error) {
      console.warn(`Error processing file ${absolutePath}:`, error.message);
      return null;
    }
  }

  // Process and store file content
  async processFileContent(fileId, absolutePath, extension) {
    try {
      const content = fs.readFileSync(absolutePath, 'utf-8');
      const lines = content.split('\n');
      const preview = content.substring(0, 1000);
      const language = this.languageMap[extension] || 'text';
      
      const contentQuery = `
        INSERT INTO repository_file_contents (
          file_id, content_text, content_preview, language_detected,
          line_count, char_count
        ) VALUES ($1, $2, $3, $4, $5, $6)
        ON CONFLICT (file_id) DO UPDATE SET
          content_text = $2,
          content_preview = $3,
          language_detected = $4,
          line_count = $5,
          char_count = $6,
          updated_at = NOW()
      `;
      
      await database.query(contentQuery, [
        fileId, content, preview, language, lines.length, content.length
      ]);
      
    } catch (error) {
      console.warn(`Error processing file content for file ID ${fileId}:`, error.message);
    }
  }

  // Complete storage process for a repository
  async completeRepositoryStorage(storageId) {
    // Calculate totals
    const statsQuery = `
      SELECT 
        COUNT(DISTINCT rd.id) as total_directories,
        COUNT(rf.id) as total_files,
        COALESCE(SUM(rf.file_size_bytes), 0) as total_size
      FROM repository_storage rs
      LEFT JOIN repository_directories rd ON rs.id = rd.storage_id
      LEFT JOIN repository_files rf ON rs.id = rf.storage_id
      WHERE rs.id = $1
    `;
    
    const statsResult = await database.query(statsQuery, [storageId]);
    const stats = statsResult.rows[0];
    
    // Update storage record
    const updateQuery = `
      UPDATE repository_storage 
      SET 
        storage_status = 'completed',
        total_files_count = $1,
        total_directories_count = $2,
        total_size_bytes = $3,
        download_completed_at = NOW(),
        updated_at = NOW()
      WHERE id = $4
      RETURNING *
    `;
    
    const result = await database.query(updateQuery, [
      parseInt(stats.total_files),
      parseInt(stats.total_directories),
      parseInt(stats.total_size),
      storageId
    ]);
    
    return result.rows[0];
  }

  // Mark storage as failed
  async markStorageFailed(storageId, error) {
    const query = `
      UPDATE repository_storage 
      SET 
        storage_status = 'error',
        updated_at = NOW()
      WHERE id = $1
      RETURNING *
    `;
    
    const result = await database.query(query, [storageId]);
    return result.rows[0];
  }

  // Get repository file structure
  async getRepositoryStructure(repositoryId, directoryPath = null) {
    let query = `
      SELECT 
        rd.*,
        COUNT(DISTINCT rdf.id) as files_count,
        COUNT(DISTINCT rds.id) as subdirs_count
      FROM repository_directories rd
      LEFT JOIN repository_files rdf ON rd.id = rdf.directory_id
      LEFT JOIN repository_directories rds ON rd.id = rds.parent_directory_id
      WHERE rd.repository_id = $1
    `;
    
    const params = [repositoryId];
    
    if (directoryPath !== null) {
      query += ` AND rd.relative_path = $2`;
      params.push(directoryPath);
    }
    
    query += ` GROUP BY rd.id ORDER BY rd.level, rd.directory_name`;
    
    const result = await database.query(query, params);
    return result.rows;
  }

  // Get files in a directory
  async getDirectoryFiles(repositoryId, directoryPath = '') {
    const query = `
      SELECT rf.*, rfc.language_detected, rfc.line_count
      FROM repository_files rf
      LEFT JOIN repository_directories rd ON rf.directory_id = rd.id
      LEFT JOIN repository_file_contents rfc ON rf.id = rfc.file_id
      WHERE rf.repository_id = $1 AND rd.relative_path = $2
      ORDER BY rf.filename
    `;
    
    const result = await database.query(query, [repositoryId, directoryPath]);
    return result.rows;
  }

  // Search files by content
  async searchFileContent(repositoryId, searchQuery) {
    const query = `
      SELECT rf.filename, rf.relative_path, rfc.language_detected,
             ts_rank_cd(to_tsvector('english', rfc.content_text), plainto_tsquery('english', $2)) as rank
      FROM repository_files rf
      JOIN repository_file_contents rfc ON rf.id = rfc.file_id
      WHERE rf.repository_id = $1 
        AND to_tsvector('english', rfc.content_text) @@ plainto_tsquery('english', $2)
      ORDER BY rank DESC, rf.filename
      LIMIT 50
    `;
    
    const result = await database.query(query, [repositoryId, searchQuery]);
    return result.rows;
  }

  // Utility methods
  isBinaryContent(buffer) {
    // Simple binary detection - check for null bytes in first 1024 bytes
    const sample = buffer.slice(0, Math.min(1024, buffer.length));
    return sample.includes(0);
  }

  getMimeType(extension, isBinary) {
    const mimeTypes = {
      '.js': 'application/javascript',
      '.ts': 'application/typescript',
      '.json': 'application/json',
      '.html': 'text/html',
      '.css': 'text/css',
      '.md': 'text/markdown',
      '.txt': 'text/plain',
      '.xml': 'application/xml',
      '.yml': 'application/x-yaml',
      '.yaml': 'application/x-yaml',
      '.pdf': 'application/pdf',
      '.png': 'image/png',
      '.jpg': 'image/jpeg',
      '.jpeg': 'image/jpeg',
      '.gif': 'image/gif',
      '.svg': 'image/svg+xml'
    };

    if (mimeTypes[extension]) {
      return mimeTypes[extension];
    }

    return isBinary ? 'application/octet-stream' : 'text/plain';
  }

  // Clean up storage for a repository
  async cleanupRepositoryStorage(repositoryId) {
    const queries = [
      'DELETE FROM repository_file_contents WHERE file_id IN (SELECT id FROM repository_files WHERE repository_id = $1)',
      'DELETE FROM repository_files WHERE repository_id = $1',
      'DELETE FROM repository_directories WHERE repository_id = $1',
      'DELETE FROM repository_storage WHERE repository_id = $1'
    ];

    for (const query of queries) {
      await database.query(query, [repositoryId]);
    }
  }
}

module.exports = FileStorageService;
