import { Type } from '@sinclair/typebox';
import type { ToolDefinition } from '@mariozechner/pi-coding-agent';
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { guardPath, guardPathUnsafe, SandboxEscapeError } from './path-guard.js';

const execAsync = promisify(exec);

const GIT_TIMEOUT_MS = 15_000;
const MAX_OUTPUT_BYTES = 100 * 1024; // 100 KB

async function runGit(
  args: string[],
  cwd?: string,
): Promise<{ stdout: string; stderr: string; error?: string }> {
  // Only allow specific safe read-only git subcommands
  const allowedSubcommands = ['status', 'log', 'diff', 'show', 'branch', 'tag', 'ls-files'];
  const subcommand = args[0];
  if (!subcommand || !allowedSubcommands.includes(subcommand)) {
    return {
      stdout: '',
      stderr: '',
      error: `Blocked: git subcommand "${subcommand}" is not allowed. Permitted: ${allowedSubcommands.join(', ')}`,
    };
  }

  const cmd = `git ${args.map((a) => JSON.stringify(a)).join(' ')}`;
  try {
    const { stdout, stderr } = await execAsync(cmd, {
      cwd,
      timeout: GIT_TIMEOUT_MS,
      maxBuffer: MAX_OUTPUT_BYTES,
    });
    return { stdout, stderr };
  } catch (err: unknown) {
    const e = err as { stdout?: string; stderr?: string; message?: string };
    return {
      stdout: e.stdout ?? '',
      stderr: e.stderr ?? '',
      error: e.message ?? String(err),
    };
  }
}

export function createGitTools(sandboxDir?: string): ToolDefinition[] {
  const defaultCwd = sandboxDir ?? process.cwd();

  const gitStatus: ToolDefinition = {
    name: 'git_status',
    label: 'Git Status',
    description: 'Show the working tree status (staged, unstaged, untracked files).',
    parameters: Type.Object({
      cwd: Type.Optional(
        Type.String({
          description: 'Repository working directory (relative to sandbox or absolute within it).',
        }),
      ),
    }),
    async execute(_toolCallId, params) {
      const { cwd } = params as { cwd?: string };
      let safeCwd: string;
      try {
        safeCwd = guardPath(cwd ?? '.', defaultCwd);
      } catch (err) {
        if (err instanceof SandboxEscapeError) {
          return {
            content: [{ type: 'text' as const, text: `Error: ${err.message}` }],
            details: undefined,
          };
        }
        return {
          content: [{ type: 'text' as const, text: `Error: ${String(err)}` }],
          details: undefined,
        };
      }
      const result = await runGit(['status', '--short', '--branch'], safeCwd);
      const text = result.error
        ? `Error: ${result.error}\n${result.stderr}`
        : result.stdout || '(no output)';
      return {
        content: [{ type: 'text' as const, text: text }],
        details: undefined,
      };
    },
  };

  const gitLog: ToolDefinition = {
    name: 'git_log',
    label: 'Git Log',
    description: 'Show recent commit history.',
    parameters: Type.Object({
      limit: Type.Optional(Type.Number({ description: 'Number of commits to show (default 20)' })),
      oneline: Type.Optional(
        Type.Boolean({ description: 'Compact one-line format (default true)' }),
      ),
      cwd: Type.Optional(
        Type.String({
          description: 'Repository working directory (relative to sandbox or absolute within it).',
        }),
      ),
    }),
    async execute(_toolCallId, params) {
      const { limit, oneline, cwd } = params as {
        limit?: number;
        oneline?: boolean;
        cwd?: string;
      };
      let safeCwd: string;
      try {
        safeCwd = guardPath(cwd ?? '.', defaultCwd);
      } catch (err) {
        if (err instanceof SandboxEscapeError) {
          return {
            content: [{ type: 'text' as const, text: `Error: ${err.message}` }],
            details: undefined,
          };
        }
        return {
          content: [{ type: 'text' as const, text: `Error: ${String(err)}` }],
          details: undefined,
        };
      }
      const args = ['log', `--max-count=${limit ?? 20}`];
      if (oneline !== false) args.push('--oneline');
      const result = await runGit(args, safeCwd);
      const text = result.error
        ? `Error: ${result.error}\n${result.stderr}`
        : result.stdout || '(no commits)';
      return {
        content: [{ type: 'text' as const, text: text }],
        details: undefined,
      };
    },
  };

  const gitDiff: ToolDefinition = {
    name: 'git_diff',
    label: 'Git Diff',
    description: 'Show changes between commits, working tree, or staged changes.',
    parameters: Type.Object({
      staged: Type.Optional(
        Type.Boolean({ description: 'Show staged (cached) changes instead of unstaged' }),
      ),
      ref: Type.Optional(
        Type.String({ description: 'Compare against this ref (commit SHA, branch, or tag)' }),
      ),
      path: Type.Optional(
        Type.String({ description: 'Limit diff to a specific file or directory' }),
      ),
      cwd: Type.Optional(
        Type.String({
          description: 'Repository working directory (relative to sandbox or absolute within it).',
        }),
      ),
    }),
    async execute(_toolCallId, params) {
      const { staged, ref, path, cwd } = params as {
        staged?: boolean;
        ref?: string;
        path?: string;
        cwd?: string;
      };
      let safeCwd: string;
      try {
        safeCwd = guardPath(cwd ?? '.', defaultCwd);
      } catch (err) {
        if (err instanceof SandboxEscapeError) {
          return {
            content: [{ type: 'text' as const, text: `Error: ${err.message}` }],
            details: undefined,
          };
        }
        return {
          content: [{ type: 'text' as const, text: `Error: ${String(err)}` }],
          details: undefined,
        };
      }
      let safePath: string | undefined;
      if (path !== undefined) {
        try {
          safePath = guardPathUnsafe(path, defaultCwd);
        } catch (err) {
          if (err instanceof SandboxEscapeError) {
            return {
              content: [{ type: 'text' as const, text: `Error: ${err.message}` }],
              details: undefined,
            };
          }
          return {
            content: [{ type: 'text' as const, text: `Error: ${String(err)}` }],
            details: undefined,
          };
        }
      }
      const args = ['diff'];
      if (staged) args.push('--cached');
      if (ref) args.push(ref);
      args.push('--');
      if (safePath !== undefined) args.push(safePath);
      const result = await runGit(args, safeCwd);
      const text = result.error
        ? `Error: ${result.error}\n${result.stderr}`
        : result.stdout || '(no diff)';
      return {
        content: [{ type: 'text' as const, text: text }],
        details: undefined,
      };
    },
  };

  return [gitStatus, gitLog, gitDiff];
}
