import fs from 'fs';
import path from 'path';
import { resolveExistingSafe, assertWithinRoot } from './security';
import { MindOSError, ErrorCodes } from '@/lib/errors';
import { cleanDirName, INSTRUCTION_TEMPLATE, README_TEMPLATE } from './space-scaffold';

/**
 * Reads the content of a file given a relative path from mindRoot.
 */
export function readFile(mindRoot: string, filePath: string): string {
  const resolved = resolveExistingSafe(mindRoot, filePath);
  return fs.readFileSync(resolved, 'utf-8');
}

/**
 * Atomically writes content to a file (temp file + rename).
 * Creates parent directories as needed.
 */
export function writeFile(mindRoot: string, filePath: string, content: string): void {
  const resolved = resolveExistingSafe(mindRoot, filePath);
  const dir = path.dirname(resolved);
  const tmp = path.join(dir, `.tmp-${Date.now()}-${path.basename(resolved)}`);
  try {
    fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(tmp, content, 'utf-8');
    fs.renameSync(tmp, resolved);
  } catch (err) {
    try { fs.unlinkSync(tmp); } catch { /* ignore */ }
    throw err;
  }
}

/**
 * Creates a new file. Throws if the file already exists.
 * Creates parent directories as needed.
 * NOTE: Does NOT auto-scaffold Space files (INSTRUCTION.md/README.md).
 * Use createSpaceFilesystem() or convertToSpace() for explicit Space creation.
 */
export function createFile(mindRoot: string, filePath: string, initialContent = ''): void {
  const resolved = resolveExistingSafe(mindRoot, filePath);
  fs.mkdirSync(path.dirname(resolved), { recursive: true });
  try {
    // 'wx' flag: create exclusively — fails atomically if file already exists (no TOCTOU race)
    fs.writeFileSync(resolved, initialContent, { encoding: 'utf-8', flag: 'wx' });
  } catch (err: unknown) {
    if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'EEXIST') {
      throw new MindOSError(ErrorCodes.FILE_ALREADY_EXISTS, `File already exists: ${filePath}`, { filePath });
    }
    throw err;
  }
}

/**
 * Deletes a file. Throws if the file does not exist.
 */
export function deleteFile(mindRoot: string, filePath: string): void {
  const resolved = resolveExistingSafe(mindRoot, filePath);
  try {
    fs.unlinkSync(resolved);
  } catch (err: unknown) {
    if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {
      throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `File not found: ${filePath}`, { filePath });
    }
    throw err;
  }
}

/**
 * Recursively deletes a directory and all its contents.
 * Throws if the path does not exist, is outside mindRoot, or is not a directory.
 */
export function deleteDirectory(mindRoot: string, dirPath: string): void {
  const resolved = resolveExistingSafe(mindRoot, dirPath);
  if (!fs.existsSync(resolved)) {
    throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Directory not found: ${dirPath}`, { dirPath });
  }
  if (!fs.statSync(resolved).isDirectory()) {
    throw new MindOSError(ErrorCodes.INVALID_PATH, `Not a directory: ${dirPath}`, { dirPath });
  }
  fs.rmSync(resolved, { recursive: true, force: true });
}

/**
 * Converts an existing directory into a Space by creating INSTRUCTION.md
 * (and README.md if missing). Idempotent — skips files that already exist.
 */
export function convertToSpace(mindRoot: string, dirPath: string): void {
  const resolved = resolveExistingSafe(mindRoot, dirPath);
  if (!fs.existsSync(resolved)) {
    throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Directory not found: ${dirPath}`, { dirPath });
  }
  if (!fs.statSync(resolved).isDirectory()) {
    throw new MindOSError(ErrorCodes.INVALID_PATH, `Not a directory: ${dirPath}`, { dirPath });
  }

  const dirName = path.basename(resolved);
  const name = cleanDirName(dirName);

  try {
    const instructionPath = path.join(resolved, 'INSTRUCTION.md');
    if (!fs.existsSync(instructionPath)) {
      fs.writeFileSync(instructionPath, INSTRUCTION_TEMPLATE(name), 'utf-8');
    }

    const readmePath = path.join(resolved, 'README.md');
    if (!fs.existsSync(readmePath)) {
      fs.writeFileSync(readmePath, README_TEMPLATE(name), 'utf-8');
    }
  } catch (err: unknown) {
    const msg = err instanceof Error ? err.message : String(err);
    throw new MindOSError(ErrorCodes.INTERNAL_ERROR, `Failed to create space files in ${dirPath}: ${msg}`, { dirPath });
  }
}

/**
 * Renames a file within its current directory.
 * newName must be a plain filename (no path separators).
 * Returns the new relative path.
 */
export function renameFile(mindRoot: string, oldPath: string, newName: string): string {
  if (newName.includes('/') || newName.includes('\\')) {
    throw new MindOSError(ErrorCodes.INVALID_PATH, 'Invalid filename: must not contain path separators', { newName });
  }
  const root = path.resolve(mindRoot);
  const oldResolved = resolveExistingSafe(mindRoot, oldPath);

  const dir = path.dirname(oldResolved);
  const newResolved = path.join(dir, newName);
  assertWithinRoot(newResolved, root);

  if (fs.existsSync(newResolved)) {
    throw new MindOSError(ErrorCodes.FILE_ALREADY_EXISTS, 'A file with that name already exists', { newName });
  }
  fs.renameSync(oldResolved, newResolved);
  return path.relative(root, newResolved).replace(/\\/g, '/');
}

/**
 * Renames a directory (Mind Space) under mindRoot. `newName` must be a single segment (no `/` or `\\`).
 * Does not rewrite links inside markdown files.
 */
export function renameSpaceDirectory(mindRoot: string, spacePath: string, newName: string): string {
  const trimmedName = newName.trim();
  if (!trimmedName || trimmedName.includes('/') || trimmedName.includes('\\')) {
    throw new MindOSError(ErrorCodes.INVALID_PATH, 'Invalid space name: must not contain path separators', { newName });
  }
  const normalized = spacePath.replace(/\/+$/g, '').trim();
  if (!normalized) {
    throw new MindOSError(ErrorCodes.INVALID_PATH, 'Space path is required', { spacePath });
  }

  const root = path.resolve(mindRoot);
  const oldResolved = resolveExistingSafe(mindRoot, normalized);
  if (!fs.existsSync(oldResolved)) {
    throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Space not found: ${normalized}`, { spacePath: normalized });
  }
  if (!fs.statSync(oldResolved).isDirectory()) {
    throw new MindOSError(ErrorCodes.INVALID_PATH, `Not a directory: ${normalized}`, { spacePath: normalized });
  }

  const parentDir = path.dirname(oldResolved);
  const newResolved = path.join(parentDir, trimmedName);
  assertWithinRoot(newResolved, root);

  if (fs.existsSync(newResolved)) {
    throw new MindOSError(ErrorCodes.FILE_ALREADY_EXISTS, 'A space with that name already exists', { newName: trimmedName });
  }

  fs.renameSync(oldResolved, newResolved);
  return path.relative(root, newResolved).replace(/\\/g, '/');
}

/**
 * Moves a file from one path to another within mindRoot.
 * Returns the new path and a list of files that referenced the old path.
 */
export function moveFile(
  mindRoot: string,
  fromPath: string,
  toPath: string,
  findBacklinksFn: (mindRoot: string, targetPath: string) => Array<{ source: string }>
): { newPath: string; affectedFiles: string[] } {
  const fromResolved = resolveExistingSafe(mindRoot, fromPath);
  const toResolved = resolveExistingSafe(mindRoot, toPath);
  if (!fs.existsSync(fromResolved)) throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Source not found: ${fromPath}`, { fromPath });
  if (fs.existsSync(toResolved)) throw new MindOSError(ErrorCodes.FILE_ALREADY_EXISTS, `Destination already exists: ${toPath}`, { toPath });
  fs.mkdirSync(path.dirname(toResolved), { recursive: true });
  fs.renameSync(fromResolved, toResolved);
  const backlinks = findBacklinksFn(mindRoot, fromPath);
  return { newPath: toPath, affectedFiles: backlinks.map(b => b.source) };
}

/**
 * Returns files sorted by modification time, descending.
 */
export function getRecentlyModified(
  mindRoot: string,
  allFiles: string[],
  limit = 10
): Array<{ path: string; mtime: number; mtimeISO: string }> {
  const withMtime = allFiles.flatMap((filePath) => {
    try {
      const abs = resolveExistingSafe(mindRoot, filePath);
      const stat = fs.statSync(abs);
      return [{ path: filePath, mtime: stat.mtimeMs, mtimeISO: stat.mtime.toISOString() }];
    } catch {
      return [];
    }
  });
  withMtime.sort((a, b) => b.mtime - a.mtime);
  return withMtime.slice(0, limit);
}
