// grok-faf-mcp/src/handlers/fileHandler.ts
// 🏎️ FAF File Operations - Production Ready Implementation

import * as fs from 'fs/promises';
import * as path from 'path';
import type { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { confineFileOp, PathConfinementError } from '../utils/safe-path';

/**
 * Security validator for file paths
 */
export class PathValidator {
  private static readonly FORBIDDEN_PATHS = ['/etc', '/sys', '/proc', '/private/etc'];
  private static readonly MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
  
  static validate(filePath: string): { valid: boolean; error?: string } {
    // Normalize and resolve path
    const normalized = path.normalize(filePath);
    const resolved = path.resolve(filePath);
    
    // Check for path traversal
    if (normalized.includes('..')) {
      return { valid: false, error: 'Path traversal detected' };
    }
    
    // Check forbidden paths
    for (const forbidden of this.FORBIDDEN_PATHS) {
      if (resolved.startsWith(forbidden)) {
        return { valid: false, error: `Access to ${forbidden} is forbidden` };
      }
    }
    
    return { valid: true };
  }
  
  static async checkFileSize(filePath: string): Promise<{ valid: boolean; error?: string }> {
    try {
      const stats = await fs.stat(filePath);
      if (stats.size > this.MAX_FILE_SIZE) {
        return { 
          valid: false, 
          error: `File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max: 50MB)` 
        };
      }
      return { valid: true };
    } catch (_error: unknown) {
      // File doesn't exist yet (for write operations)
      return { valid: true };
    }
  }
}

/**
 * FAF Read Tool Definition
 */
export const fafReadTool: Tool = {
  name: 'faf_read',
  description: 'Read content from any file on the local filesystem',
  inputSchema: {
    type: 'object',
    properties: {
      path: {
        type: 'string',
        description: 'Absolute or relative file path to read'
      }
    },
    required: ['path']
  }
};

/**
 * FAF Write Tool Definition
 */
export const fafWriteTool: Tool = {
  name: 'faf_write',
  description: 'Write content to any file on the local filesystem',
  inputSchema: {
    type: 'object',
    properties: {
      path: {
        type: 'string',
        description: 'Absolute or relative file path to write'
      },
      content: {
        type: 'string',
        description: 'Content to write to the file'
      }
    },
    required: ['path', 'content']
  }
};

/**
 * Handle faf_read tool execution
 */
export async function handleFafRead(args: any): Promise<CallToolResult> {
  const startTime = Date.now();
  
  try {
    const { path: rawPath } = args;

    // Confine to the project root(s) — any file type, but no escape to
    // /etc, ~/.ssh, or via ../ traversal (CWE-22). Returns symlink-canonical path.
    let filePath: string;
    try {
      filePath = confineFileOp(rawPath);
    } catch (err) {
      if (err instanceof PathConfinementError) {
        return {
          content: [{ type: 'text', text: `❌ Security error: ${err.message}` }],
          isError: true,
        };
      }
      throw err;
    }

    // Check file size
    const sizeValidation = await PathValidator.checkFileSize(filePath);
    if (!sizeValidation.valid) {
      return {
        content: [{
          type: 'text',
          text: `❌ ${sizeValidation.error}`
        }],
        isError: true
      };
    }
    
    // Read file with timeout
    const content = await Promise.race([
      fs.readFile(filePath, 'utf8'),
      new Promise<never>((_, reject) => 
        setTimeout(() => reject(new Error('Read timeout (30s)')), 30000)
      )
    ]);
    
    const duration = Date.now() - startTime;
    const stats = await fs.stat(filePath);
    
    return {
      content: [{
        type: 'text',
        text: content
      }],
      metadata: {
        duration_ms: duration,
        file_size: stats.size,
        file_path: path.resolve(filePath),
        message: `✅ Read ${stats.size} bytes in ${duration}ms`
      }
    };
    
  } catch (error: any) {
    return {
      content: [{
        type: 'text',
        text: `❌ Failed to read file: ${error.message}`
      }],
      isError: true
    };
  }
}

/**
 * Handle faf_write tool execution
 */
export async function handleFafWrite(args: any): Promise<CallToolResult> {
  const startTime = Date.now();
  
  try {
    const { path: rawPath, content } = args;

    // Confine to the project root(s) before any write — closes arbitrary file
    // write outside the project (e.g. overwriting ~/.bashrc). Note: for a
    // not-yet-existing file confineFileOp validates the lexical path within root.
    let filePath: string;
    try {
      filePath = confineFileOp(rawPath);
    } catch (err) {
      if (err instanceof PathConfinementError) {
        return {
          content: [{ type: 'text', text: `❌ Security error: ${err.message}` }],
          isError: true,
        };
      }
      throw err;
    }

    // Check content size
    const contentSize = Buffer.byteLength(content, 'utf8');
    if (contentSize > 50 * 1024 * 1024) {
      return {
        content: [{
          type: 'text',
          text: `❌ Content too large: ${(contentSize / 1024 / 1024).toFixed(2)}MB (max: 50MB)`
        }],
        isError: true
      };
    }
    
    // Create directory if needed
    const dir = path.dirname(filePath);
    await fs.mkdir(dir, { recursive: true });
    
    // Write file with timeout
    await Promise.race([
      fs.writeFile(filePath, content, 'utf8'),
      new Promise<never>((_, reject) => 
        setTimeout(() => reject(new Error('Write timeout (30s)')), 30000)
      )
    ]);
    
    const duration = Date.now() - startTime;
    
    return {
      content: [{
        type: 'text',
        text: `✅ Successfully wrote ${contentSize} bytes to ${path.resolve(filePath)}`
      }],
      metadata: {
        duration_ms: duration,
        bytes_written: contentSize,
        file_path: path.resolve(filePath),
        message: `✅ Write completed in ${duration}ms`
      }
    };
    
  } catch (error: any) {
    return {
      content: [{
        type: 'text',
        text: `❌ Failed to write file: ${error.message}`
      }],
      isError: true
    };
  }
}

// Export handlers
export const fileHandlers = {
  faf_read: handleFafRead,
  faf_write: handleFafWrite
};
