import fs from 'node:fs';
import path from 'node:path';
import { appendAudit } from '../../runtime.js';
import { createApprovalRequest, consumeApprovalRequest } from '../../approvals.js';
import { redactToolOutput, resolveSafePath, isReadonlyPath } from '../safety.js';

const SKIP_DIRS = new Set(['.git', 'node_modules', '.next', 'data']);

function audit(tool, status, meta = {}) {
  appendAudit({ type: `local.${tool}`, summary: `${tool} ${status}`, status, meta });
}

function approval(tool, input, absPath) {
  return createApprovalRequest({
    action_type: tool,
    risk: 'medium',
    summary: `${tool} requested for ${absPath}`,
    payload: { input: { ...input, content: input.content ? `[${String(input.content).length} chars]` : undefined }, path: absPath },
  });
}

function approvalPathMatches(absPath) {
  return payload => payload.path === absPath;
}

export async function fileRead(input = {}) {
  const { path: absPath } = resolveSafePath(input.path);
  const maxBytes = Number(process.env.MADARX_FILE_READ_MAX_BYTES || 100000);
  const stat = fs.statSync(absPath);
  if (!stat.isFile()) throw new Error('Path is not a file');
  if (stat.size > maxBytes) throw new Error(`File too large: ${stat.size} bytes`);
  const lines = fs.readFileSync(absPath, 'utf8').split(/\r?\n/);
  const offset = Math.max(1, Number(input.offset || 1));
  const limit = Math.max(1, Math.min(2000, Number(input.limit || 500)));
  const content = lines.slice(offset - 1, offset - 1 + limit).join('\n');
  audit('file_read', 'complete', { path: absPath, bytes: Buffer.byteLength(content) });
  return { ok: true, tool: 'file_read', path: absPath, content: redactToolOutput(content), metadata: { line_count: lines.length, offset, limit } };
}

export async function directoryList(input = {}) {
  const { path: absPath } = resolveSafePath(input.path || '.');
  const limit = Math.max(1, Math.min(500, Number(input.limit || 100)));
  const entries = [];
  for (const entry of fs.readdirSync(absPath, { withFileTypes: true })) {
    if (entries.length >= limit) break;
    try {
      resolveSafePath(path.join(absPath, entry.name));
    } catch {
      continue;
    }
    if (SKIP_DIRS.has(entry.name)) continue;
    entries.push({
      name: entry.name,
      type: entry.isDirectory() ? 'directory' : entry.isFile() ? 'file' : 'other',
    });
  }
  audit('directory_list', 'complete', { path: absPath, count: entries.length });
  return { ok: true, tool: 'directory_list', path: absPath, entries, metadata: { count: entries.length } };
}

export function fileWritePreflight(input = {}) {
  const { path: absPath } = resolveSafePath(input.path);
  if (isReadonlyPath(absPath)) throw new Error(`Write access denied: path is under a read-only root: ${input.path}`);
  return { absPath };
}

export async function fileWrite(input = {}, options = {}) {
  const { absPath } = fileWritePreflight(input);
  if (!options.owner_trusted && !consumeApprovalRequest(input.approval_id, { action_type: 'file_write', matches: approvalPathMatches(absPath) })) {
    const req = approval('file_write', input, absPath);
    return { ok: false, status: 'approval_required', approval_request_id: req.id, reason: 'File writes require approval' };
  }
  if (input.create_dirs !== false) fs.mkdirSync(path.dirname(absPath), { recursive: true });
  fs.writeFileSync(absPath, String(input.content ?? ''), 'utf8');
  audit('file_write', 'complete', { path: absPath });
  return { ok: true, tool: 'file_write', path: absPath, metadata: { bytes: Buffer.byteLength(String(input.content ?? '')) } };
}

export function filePatchPreflight(input = {}) {
  const { path: absPath } = resolveSafePath(input.path);
  if (isReadonlyPath(absPath)) throw new Error(`Write access denied: path is under a read-only root: ${input.path}`);
  const oldString = String(input.old_string ?? '');
  if (!oldString) throw new Error('old_string is required');
  const content = fs.readFileSync(absPath, 'utf8');
  const matches = content.split(oldString).length - 1;
  if (!matches) throw new Error('old_string not found');
  if (matches > 1 && !input.replace_all) throw new Error('old_string is ambiguous');
  return { absPath, content, matches, oldString };
}

export async function filePatch(input = {}, options = {}) {
  const { absPath, content, matches, oldString } = filePatchPreflight(input);
  const newString = String(input.new_string ?? '');
  if (!options.owner_trusted && !consumeApprovalRequest(input.approval_id, { action_type: 'file_patch', matches: approvalPathMatches(absPath) })) {
    const req = approval('file_patch', { ...input, new_string: `[${newString.length} chars]` }, absPath);
    return { ok: false, status: 'approval_required', approval_request_id: req.id, reason: 'File patches require approval' };
  }
  fs.writeFileSync(absPath, input.replace_all ? content.split(oldString).join(newString) : content.replace(oldString, newString), 'utf8');
  audit('file_patch', 'complete', { path: absPath, replacements: input.replace_all ? matches : 1 });
  return { ok: true, tool: 'file_patch', path: absPath, metadata: { replacements: input.replace_all ? matches : 1 } };
}

function walk(dir, files = [], limit = 1000) {
  if (files.length >= limit) return files;
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (SKIP_DIRS.has(entry.name)) continue;
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) walk(full, files, limit);
    else if (entry.isFile()) files.push(full);
    if (files.length >= limit) break;
  }
  return files;
}

export async function fileSearch(input = {}) {
  const { path: absPath } = resolveSafePath(input.path || '.');
  const pattern = String(input.pattern || '');
  if (!pattern) throw new Error('pattern is required');
  const limit = Math.max(1, Math.min(200, Number(input.limit || 50)));
  const re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
  const target = input.target || 'content';
  const fileGlob = input.file_glob ? String(input.file_glob) : null;
  const globRe = fileGlob
    ? new RegExp('^' + fileGlob.split('*').map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*') + '$', 'i')
    : null;
  const results = [];
  for (const file of walk(absPath, [], 5000)) {
    if (results.length >= limit) break;
    try { resolveSafePath(file); } catch { continue; }
    if (globRe && !globRe.test(path.basename(file))) continue;
    if (target === 'path' || target === 'name') {
      if (re.test(target === 'name' ? path.basename(file) : file)) results.push({ path: file });
      continue;
    }
    const stat = fs.statSync(file);
    if (stat.size > 250000 || /\.sqlite/.test(file) || /^\.env/.test(path.basename(file))) continue;
    const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
    lines.forEach((line, idx) => {
      if (results.length < limit && re.test(line)) results.push({ path: file, line: idx + 1, text: redactToolOutput(line) });
    });
  }
  audit('file_search', 'complete', { path: absPath, count: results.length });
  return { ok: true, tool: 'file_search', path: absPath, results, metadata: { count: results.length } };
}
