import { z } from 'zod';
import { execa, type ExecaError } from 'execa';
import { resolve } from 'path';
import { defineTool, type ToolResult } from './types.js';

/**
 * Patterns for dangerous commands that should be blocked
 */
const BLOCKED_PATTERNS = [
  /rm\s+(-[rf]+\s+)*[\/~]/i, // rm -rf / or ~
  /:\s*\(\)\s*\{\s*:\|:/,     // Fork bomb
  />\s*\/dev\/sd/,            // Writing to block devices
  /mkfs\./,                   // Formatting filesystems
  /dd\s+.*of=\/dev/,          // dd to devices
  /chmod\s+777\s+\//,         // chmod 777 on root
];

/**
 * Check if a command is potentially dangerous
 */
function isDangerousCommand(command: string): string | null {
  for (const pattern of BLOCKED_PATTERNS) {
    if (pattern.test(command)) {
      return 'Command matches a blocked pattern for safety reasons';
    }
  }
  return null;
}

interface CommandOutput {
  stdout: string;
  stderr: string;
  exitCode: number;
  timedOut: boolean;
}

/**
 * Execute a shell command
 */
export const executeCommandTool = defineTool({
  name: 'execute_command',
  description:
    'Execute a shell command. Use for build tools, tests, git operations, npm/yarn commands. Commands have a timeout and output is truncated if too long.',
  category: 'shell',
  schema: z.object({
    command: z.string().describe('The command to execute'),
    cwd: z.string().optional().describe('Working directory (relative to repo root)'),
    timeout: z.number().optional().default(120000).describe('Timeout in milliseconds (max 600000)'),
    env: z.record(z.string()).optional().describe('Additional environment variables'),
  }),

  async execute(input, context): Promise<ToolResult<CommandOutput>> {
    // Safety check
    const danger = isDangerousCommand(input.command);
    if (danger) {
      return { success: false, error: danger };
    }

    const cwd = input.cwd
      ? resolve(context.repoRoot, input.cwd)
      : context.repoRoot;

    if (!cwd.startsWith(context.repoRoot)) {
      return { success: false, error: 'Working directory is outside repository' };
    }

    // Clamp timeout
    const timeout = Math.min(input.timeout ?? 120000, 600000);

    try {
      const { stdout, stderr, exitCode } = await execa('bash', ['-c', input.command], {
        cwd,
        timeout,
        env: { ...process.env, ...input.env },
        reject: false,
        signal: context.abortSignal,
      });

      // Truncate long output
      const maxOutput = 50000;
      const truncatedStdout = stdout.length > maxOutput
        ? stdout.slice(0, maxOutput) + '\n... (output truncated)'
        : stdout;
      const truncatedStderr = stderr.length > maxOutput / 5
        ? stderr.slice(0, maxOutput / 5) + '\n... (stderr truncated)'
        : stderr;

      return {
        success: exitCode === 0,
        data: {
          stdout: truncatedStdout,
          stderr: truncatedStderr,
          exitCode: exitCode ?? -1,
          timedOut: false,
        },
      };
    } catch (err) {
      const execaErr = err as ExecaError;

      if (execaErr.timedOut) {
        return {
          success: false,
          data: {
            stdout: execaErr.stdout ?? '',
            stderr: execaErr.stderr ?? '',
            exitCode: -1,
            timedOut: true,
          },
          error: `Command timed out after ${timeout}ms`,
        };
      }

      if (execaErr.killed) {
        return {
          success: false,
          error: 'Command was killed (possibly due to abort signal)',
        };
      }

      return {
        success: false,
        error: `Command failed: ${execaErr.message}`,
      };
    }
  },
});

/**
 * Dangerous git operations that should be blocked or warned
 */
const DANGEROUS_GIT_OPS = [
  'push --force',
  'push -f',
  'reset --hard',
  'clean -fd',
  'checkout -f',
  'branch -D',
];

interface GitOutput {
  stdout: string;
  stderr: string;
}

/**
 * Execute a git command with safety checks
 */
export const gitCommandTool = defineTool({
  name: 'git_command',
  description:
    'Execute a git command with safety checks. Blocks dangerous operations like force push and hard reset.',
  category: 'shell',
  schema: z.object({
    args: z.array(z.string()).describe('Git command arguments (e.g., ["status", "-s"])'),
  }),

  async execute(input, context): Promise<ToolResult<GitOutput>> {
    const argStr = input.args.join(' ');

    // Check for dangerous operations
    for (const dangerous of DANGEROUS_GIT_OPS) {
      if (argStr.includes(dangerous)) {
        return {
          success: false,
          error: `Dangerous git operation blocked: "${dangerous}". This operation could cause data loss.`,
          suggestions: [
            'Use safer alternatives',
            'If this is intentional, use execute_command with explicit --force',
          ],
        };
      }
    }

    try {
      const { stdout, stderr, exitCode } = await execa('git', input.args, {
        cwd: context.repoRoot,
        reject: false,
        timeout: 60000, // 1 minute timeout for git
      });

      return {
        success: exitCode === 0,
        data: { stdout, stderr },
        error: exitCode !== 0 ? `Git command failed with exit code ${exitCode}` : undefined,
      };
    } catch (err) {
      const error = err as Error;
      return {
        success: false,
        error: `Git command failed: ${error.message}`,
      };
    }
  },
});
