/**
 * 文件系统工具（4 个）
 *
 * read_file, write_file, list_directory, search_files
 *
 * @see docs/MetonaAI-Desktop 架构与交互设计.html — 9 个基础工具
 * @see standard/开发规范.md — 使用 fs/path 内置模块
 */

import { readFile, writeFile, readdir, stat, appendFile } from 'fs/promises';
import { join, relative, resolve } from 'path';
import { existsSync } from 'fs';
import type { IMetonaTool, ToolExecutionContext } from '../../types/metona-tool';
import type { MetonaToolDef } from '../../../harness/types';
import { MetonaToolCategory, MetonaRiskLevel } from '../../../harness/types';

// ===== 1. read_file =====

export class ReadFileTool implements IMetonaTool {
  readonly definition: MetonaToolDef = {
    name: 'read_file',
    description: 'Read the contents of a file. Returns the full text content. Supports line offset and limit for large files.',
    parameters: {
      type: 'object',
      properties: {
        file_path: { type: 'string', description: 'Absolute or relative path to the file to read' },
        offset: { type: 'number', description: 'Start line number (1-indexed, default 1)' },
        limit: { type: 'number', description: 'Maximum lines to read (default 500, max 2000)' },
      },
      required: ['file_path'],
    },
    category: MetonaToolCategory.FILESYSTEM,
    riskLevel: MetonaRiskLevel.SAFE,
    requiresPermission: false,
    timeoutMs: 10_000,
  };

  async execute(args: Record<string, unknown>, context: ToolExecutionContext): Promise<unknown> {
    const filePath = this.resolvePath(args.file_path as string, context.workspacePath);
    const offset = Math.max(1, (args.offset as number) ?? 1);
    const limit = Math.min(2000, Math.max(1, (args.limit as number) ?? 500));

    const content = await readFile(filePath, 'utf-8');
    const lines = content.split('\n');
    const slicedLines = lines.slice(offset - 1, offset - 1 + limit);

    return {
      content: slicedLines.join('\n'),
      total_lines: lines.length,
      returned_lines: slicedLines.length,
      truncated: lines.length > offset - 1 + limit,
      file_size: content.length,
    };
  }

  private resolvePath(filePath: string, workspacePath: string): string {
    const resolved = resolve(workspacePath, filePath);
    // 安全检查：路径遍历防护
    if (!resolved.startsWith(resolve(workspacePath))) {
      throw new Error(`Path traversal detected: ${filePath}`);
    }
    return resolved;
  }
}

// ===== 2. write_file =====

export class WriteFileTool implements IMetonaTool {
  readonly definition: MetonaToolDef = {
    name: 'write_file',
    description: 'Write content to a file. Creates the file if it doesn\'t exist, overwrites if it does. Supports append mode.',
    parameters: {
      type: 'object',
      properties: {
        file_path: { type: 'string', description: 'Path to the file to write' },
        content: { type: 'string', description: 'Content to write to the file' },
        mode: { type: 'string', description: 'Write mode: "overwrite" (default) or "append"', enum: ['overwrite', 'append'] },
      },
      required: ['file_path', 'content'],
    },
    category: MetonaToolCategory.FILESYSTEM,
    riskLevel: MetonaRiskLevel.MEDIUM,
    requiresPermission: true,
    timeoutMs: 15_000,
  };

  async execute(args: Record<string, unknown>, context: ToolExecutionContext): Promise<unknown> {
    const filePath = this.resolvePath(args.file_path as string, context.workspacePath);
    const content = args.content as string;
    const mode = (args.mode as string) ?? 'overwrite';

    if (mode === 'append') {
      await appendFile(filePath, content, 'utf-8');
    } else {
      await writeFile(filePath, content, 'utf-8');
    }

    return { bytes_written: Buffer.byteLength(content, 'utf-8'), path: filePath };
  }

  private resolvePath(filePath: string, workspacePath: string): string {
    const resolved = resolve(workspacePath, filePath);
    if (!resolved.startsWith(resolve(workspacePath))) {
      throw new Error(`Path traversal detected: ${filePath}`);
    }
    return resolved;
  }
}

// ===== 3. list_directory =====

export class ListDirectoryTool implements IMetonaTool {
  readonly definition: MetonaToolDef = {
    name: 'list_directory',
    description: 'List the contents of a directory. Supports recursive depth control and glob filtering.',
    parameters: {
      type: 'object',
      properties: {
        dir_path: { type: 'string', description: 'Directory path (default: workspace root)' },
        depth: { type: 'number', description: 'Recursive depth (default 1, max 5)' },
        glob: { type: 'string', description: 'Filename filter pattern (e.g., "*.ts")' },
      },
    },
    category: MetonaToolCategory.FILESYSTEM,
    riskLevel: MetonaRiskLevel.SAFE,
    requiresPermission: false,
    timeoutMs: 10_000,
  };

  async execute(args: Record<string, unknown>, context: ToolExecutionContext): Promise<unknown> {
    const dirPath = args.dir_path
      ? this.resolvePath(args.dir_path as string, context.workspacePath)
      : context.workspacePath;
    const depth = Math.min(5, Math.max(1, (args.depth as number) ?? 1));
    const glob = args.glob as string | undefined;

    const entries = await this.listDir(dirPath, depth, glob, 0);
    return { entries, count: entries.length };
  }

  private async listDir(dirPath: string, maxDepth: number, glob: string | undefined, currentDepth: number): Promise<Array<{ name: string; path: string; type: string; size?: number }>> {
    const results: Array<{ name: string; path: string; type: string; size?: number }> = [];

    try {
      const entries = await readdir(dirPath, { withFileTypes: true });

      for (const entry of entries) {
        // 跳过隐藏文件和 node_modules
        if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;

        const fullPath = join(dirPath, entry.name);
        const relativePath = relative(dirPath, fullPath);

        // glob 过滤
        if (glob && !this.matchGlob(entry.name, glob)) continue;

        if (entry.isDirectory()) {
          results.push({ name: entry.name, path: relativePath, type: 'directory' });
          if (currentDepth < maxDepth - 1) {
            const subEntries = await this.listDir(fullPath, maxDepth, glob, currentDepth + 1);
            results.push(...subEntries);
          }
        } else {
          const stats = await stat(fullPath);
          results.push({ name: entry.name, path: relativePath, type: 'file', size: stats.size });
        }
      }
    } catch {
      // 目录读取失败，跳过
    }

    return results;
  }

  private matchGlob(name: string, glob: string): boolean {
    const pattern = glob.replace(/\*/g, '.*').replace(/\?/g, '.');
    return new RegExp(`^${pattern}$`, 'i').test(name);
  }

  private resolvePath(filePath: string, workspacePath: string): string {
    const resolved = resolve(workspacePath, filePath);
    if (!resolved.startsWith(resolve(workspacePath))) {
      throw new Error(`Path traversal detected: ${filePath}`);
    }
    return resolved;
  }
}

// ===== 4. search_files =====

export class SearchFilesTool implements IMetonaTool {
  readonly definition: MetonaToolDef = {
    name: 'search_files',
    description: 'Search for files by name pattern or content regex. Uses efficient file scanning.',
    parameters: {
      type: 'object',
      properties: {
        pattern: { type: 'string', description: 'Search pattern (regex for content, glob for filenames)' },
        target: { type: 'string', description: '"content" (default) to search file contents, "files" to search filenames', enum: ['content', 'files'] },
        path: { type: 'string', description: 'Search directory (default: workspace root)' },
        file_glob: { type: 'string', description: 'Limit to specific file types (e.g., "*.py")' },
        limit: { type: 'number', description: 'Maximum results (default 50)' },
      },
      required: ['pattern'],
    },
    category: MetonaToolCategory.FILESYSTEM,
    riskLevel: MetonaRiskLevel.SAFE,
    requiresPermission: false,
    timeoutMs: 30_000,
  };

  async execute(args: Record<string, unknown>, context: ToolExecutionContext): Promise<unknown> {
    const pattern = args.pattern as string;
    const target = (args.target as string) ?? 'content';
    const searchPath = args.path
      ? this.resolvePath(args.path as string, context.workspacePath)
      : context.workspacePath;
    const fileGlob = args.file_glob as string | undefined;
    const limit = Math.min(200, Math.max(1, (args.limit as number) ?? 50));

    if (target === 'files') {
      return this.searchByFilename(searchPath, pattern, fileGlob, limit);
    }
    return this.searchByContent(searchPath, pattern, fileGlob, limit);
  }

  private async searchByFilename(dirPath: string, pattern: string, fileGlob: string | undefined, limit: number): Promise<unknown> {
    const results: Array<{ path: string; name: string }> = [];
    const globPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
    const regex = new RegExp(globPattern, 'i');

    await this.walkDir(dirPath, async (filePath, name) => {
      if (results.length >= limit) return;
      if (regex.test(name)) {
        results.push({ path: relative(dirPath, filePath), name });
      }
    }, fileGlob);

    return { results, count: results.length };
  }

  private async searchByContent(dirPath: string, pattern: string, fileGlob: string | undefined, limit: number): Promise<unknown> {
    const results: Array<{ path: string; line: number; match: string }> = [];
    const regex = new RegExp(pattern, 'gi');

    await this.walkDir(dirPath, async (filePath) => {
      if (results.length >= limit) return;
      try {
        const content = await readFile(filePath, 'utf-8');
        const lines = content.split('\n');
        for (let i = 0; i < lines.length; i++) {
          if (results.length >= limit) break;
          if (regex.test(lines[i])) {
            results.push({
              path: relative(dirPath, filePath),
              line: i + 1,
              match: lines[i].trim().slice(0, 200),
            });
          }
          regex.lastIndex = 0;
        }
      } catch {
        // 跳过不可读文件
      }
    }, fileGlob);

    return { results, count: results.length };
  }

  private async walkDir(
    dirPath: string,
    callback: (filePath: string, name: string) => Promise<void>,
    fileGlob?: string,
  ): Promise<void> {
    try {
      const entries = await readdir(dirPath, { withFileTypes: true });
      for (const entry of entries) {
        if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;

        const fullPath = join(dirPath, entry.name);
        if (entry.isDirectory()) {
          await this.walkDir(fullPath, callback, fileGlob);
        } else {
          if (fileGlob && !this.matchGlob(entry.name, fileGlob)) continue;
          await callback(fullPath, entry.name);
        }
      }
    } catch {
      // 目录读取失败
    }
  }

  private matchGlob(name: string, glob: string): boolean {
    const pattern = glob.replace(/\*/g, '.*').replace(/\?/g, '.');
    return new RegExp(`^${pattern}$`, 'i').test(name);
  }

  private resolvePath(filePath: string, workspacePath: string): string {
    const resolved = resolve(workspacePath, filePath);
    if (!resolved.startsWith(resolve(workspacePath))) {
      throw new Error(`Path traversal detected: ${filePath}`);
    }
    return resolved;
  }
}
