import { resolve, join, relative, isAbsolute } from 'path';
import { existsSync, mkdirSync, realpathSync, statSync, readFileSync, writeFileSync } from 'fs';
import { homedir } from 'os';
import { StoragePaths, ensureStorageDir, LegacyPaths } from '../config/storage-paths.js';

/**
 * Validation result for path operations
 */
export interface PathValidationResult {
  valid: boolean;
  path: string | null;
  error: string | null;
}

/**
 * Options for path validation
 */
export interface ValidatePathOptions {
  baseDir?: string | null;
  mustExist?: boolean;
  allowHome?: boolean;
}

/**
 * Normalize path separators to the native format for the current platform
 * - Windows: converts / to \
 * - Linux/macOS: converts \ to /
 * @param inputPath - Path with potentially mixed separators
 * @returns Path with native separators
 */
export function normalizePathSeparators(inputPath: string): string {
  if (!inputPath) return inputPath;

  if (process.platform === 'win32') {
    // Windows: convert forward slashes to backslashes
    return inputPath.replace(/\//g, '\\');
  } else {
    // Linux/macOS: convert backslashes to forward slashes
    // This handles Windows-style paths being used on Unix systems
    return inputPath.replace(/\\/g, '/');
  }
}

/**
 * Resolve a path, handling ~ for home directory
 * Also handles Windows drive-relative paths (e.g., "D:path" -> "D:\path")
 * and normalizes mixed slashes for cross-platform compatibility
 *
 * Cross-platform behavior:
 * - Windows: D:/path/to/file -> D:\path\to\file
 * - Linux/macOS: /path\to/file -> /path/to/file
 *
 * @param inputPath - Path to resolve (can use / or \ on any platform)
 * @returns Absolute path with native separators
 */
export function resolvePath(inputPath: string): string {
  if (!inputPath) return process.cwd();

  // Handle ~ for home directory (before normalizing separators)
  if (inputPath.startsWith('~')) {
    const remainder = inputPath.slice(1);
    return join(homedir(), normalizePathSeparators(remainder));
  }

  // Normalize path separators to native format
  inputPath = normalizePathSeparators(inputPath);

  // Handle Windows drive-relative paths (e.g., "D:path" without backslash)
  // Pattern: single letter followed by colon, then immediately a non-slash character
  // This converts "D:path" to "D:\path" to make it absolute
  // Only apply on Windows or when path looks like a Windows drive path
  if (process.platform === 'win32') {
    const driveRelativeMatch = inputPath.match(/^([a-zA-Z]:)([^\\].*)$/);
    if (driveRelativeMatch) {
      // Insert backslash after drive letter
      inputPath = driveRelativeMatch[1] + '\\' + driveRelativeMatch[2];
    }
  }

  return resolve(inputPath);
}

/**
 * Validate and sanitize a user-provided path
 * Prevents path traversal attacks and validates path is within allowed boundaries
 * @param inputPath - User-provided path
 * @param options - Validation options
 * @returns Validation result with path or error
 */
export function validatePath(inputPath: string, options: ValidatePathOptions = {}): PathValidationResult {
  const { baseDir = null, mustExist = false, allowHome = true } = options;

  // Check for empty/null input
  if (!inputPath || typeof inputPath !== 'string') {
    return { valid: false, path: null, error: 'Path is required' };
  }

  // Trim whitespace
  const trimmedPath = inputPath.trim();

  // Check for suspicious patterns (null bytes, control characters)
  if (/[\x00-\x1f]/.test(trimmedPath)) {
    return { valid: false, path: null, error: 'Path contains invalid characters' };
  }

  // Resolve the path
  let resolvedPath: string;
  try {
    resolvedPath = resolvePath(trimmedPath);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { valid: false, path: null, error: `Invalid path: ${message}` };
  }

  // Check if within base directory when specified (pre-symlink resolution)
  const resolvedBase = baseDir ? resolvePath(baseDir) : null;
  if (resolvedBase) {
    const relativePath = relative(resolvedBase, resolvedPath);

    // Path traversal detection: relative path should not start with '..'
    if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
      return {
        valid: false,
        path: null,
        error: `Path must be within ${resolvedBase}`
      };
    }
  }

  // Check if path exists when required
  if (mustExist && !existsSync(resolvedPath)) {
    return { valid: false, path: null, error: `Path does not exist: ${resolvedPath}` };
  }

  // Get real path if it exists (resolves symlinks)
  let realPath = resolvedPath;
  if (existsSync(resolvedPath)) {
    try {
      realPath = realpathSync(resolvedPath);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      return { valid: false, path: null, error: `Cannot resolve path: ${message}` };
    }
  } else if (resolvedBase) {
    // For non-existent paths, resolve the nearest existing ancestor to prevent symlink-based escapes
    // (e.g., baseDir/link/newfile where baseDir/link is a symlink to a disallowed location).
    let existingPath = resolvedPath;
    while (!existsSync(existingPath)) {
      const parent = resolve(existingPath, '..');
      if (parent === existingPath) break;
      existingPath = parent;
    }

    if (existsSync(existingPath)) {
      try {
        const realExisting = realpathSync(existingPath);
        const remainder = relative(existingPath, resolvedPath);
        realPath = remainder && remainder !== '.' ? join(realExisting, remainder) : realExisting;
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return { valid: false, path: null, error: `Cannot resolve path: ${message}` };
      }
    }
  }

  // Check if within base directory when specified (post-symlink resolution)
  if (resolvedBase) {
    const relativePath = relative(resolvedBase, realPath);

    // Path traversal detection: relative path should not start with '..'
    if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
      return {
        valid: false,
        path: null,
        error: `Path must be within ${resolvedBase}`
      };
    }
  }

  // Check home directory restriction
  if (!allowHome) {
    const home = homedir();
    if (realPath === home || realPath.startsWith(home + '/') || realPath.startsWith(home + '\\')) {
      // This is fine, we're just checking if it's explicitly the home dir itself
    }
  }

  return { valid: true, path: realPath, error: null };
}

/**
 * Validate output file path for writing
 * @param outputPath - Output file path
 * @param defaultDir - Default directory if path is relative
 * @returns Validation result with path or error
 */
export function validateOutputPath(outputPath: string, defaultDir: string = process.cwd()): PathValidationResult {
  if (!outputPath || typeof outputPath !== 'string') {
    return { valid: false, path: null, error: 'Output path is required' };
  }

  const trimmedPath = outputPath.trim();

  // Check for suspicious patterns
  if (/[\x00-\x1f]/.test(trimmedPath)) {
    return { valid: false, path: null, error: 'Output path contains invalid characters' };
  }

  // Resolve the path
  let resolvedPath: string;
  try {
    resolvedPath = isAbsolute(trimmedPath) ? trimmedPath : join(defaultDir, trimmedPath);
    resolvedPath = resolve(resolvedPath);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { valid: false, path: null, error: `Invalid output path: ${message}` };
  }

  // Ensure it's not a directory
  if (existsSync(resolvedPath)) {
    try {
      const stat = statSync(resolvedPath);
      if (stat.isDirectory()) {
        return { valid: false, path: null, error: 'Output path is a directory, expected a file' };
      }
    } catch {
      // Ignore stat errors
    }
  }

  return { valid: true, path: resolvedPath, error: null };
}

/**
 * Get potential template locations
 * @returns Array of existing template directories
 */
export function getTemplateLocations(): string[] {
  const locations = [
    join(homedir(), '.claude', 'templates'),
    join(process.cwd(), '.claude', 'templates')
  ];

  return locations.filter(loc => existsSync(loc));
}

/**
 * Find a template file in known locations
 * @param templateName - Name of template file (e.g., 'workflow-dashboard.html')
 * @returns Path to template or null if not found
 */
export function findTemplate(templateName: string): string | null {
  const locations = getTemplateLocations();

  for (const loc of locations) {
    const templatePath = join(loc, templateName);
    if (existsSync(templatePath)) {
      return templatePath;
    }
  }

  return null;
}

/**
 * Ensure directory exists, creating if necessary
 * @param dirPath - Directory path to ensure
 */
export function ensureDir(dirPath: string): void {
  if (!existsSync(dirPath)) {
    mkdirSync(dirPath, { recursive: true });
  }
}

/**
 * Normalize path for display (handle Windows backslashes)
 * @param filePath - Path to normalize
 * @returns Normalized path with forward slashes
 */
export function normalizePathForDisplay(filePath: string): string {
  return filePath.replace(/\\/g, '/');
}

// Recent paths storage - uses centralized storage with backward compatibility
const MAX_RECENT_PATHS = 10;

/**
 * Get the recent paths file location
 * Uses new location but falls back to legacy location for backward compatibility
 */
function getRecentPathsFile(): string {
  const newPath = StoragePaths.global.recentPaths();
  const legacyPath = LegacyPaths.recentPaths();

  // Backward compatibility: use legacy if it exists and new doesn't
  if (!existsSync(newPath) && existsSync(legacyPath)) {
    return legacyPath;
  }
  return newPath;
}

/**
 * Recent paths data structure
 */
interface RecentPathsData {
  paths: string[];
}

/**
 * Get recent project paths
 * @returns Array of recent paths
 */
export function getRecentPaths(): string[] {
  try {
    const recentPathsFile = getRecentPathsFile();
    if (existsSync(recentPathsFile)) {
      const content = readFileSync(recentPathsFile, 'utf8');
      const data = JSON.parse(content) as RecentPathsData;
      return Array.isArray(data.paths) ? data.paths : [];
    }
  } catch {
    // Ignore errors, return empty array
  }
  return [];
}

/**
 * Track a project path (add to recent paths)
 * @param projectPath - Path to track
 */
export function trackRecentPath(projectPath: string): void {
  try {
    const normalized = normalizePathForDisplay(resolvePath(projectPath));
    let paths = getRecentPaths();

    // Remove if already exists (will be added to front)
    paths = paths.filter(p => normalizePathForDisplay(p) !== normalized);

    // Add to front
    paths.unshift(normalized);

    // Limit to max
    paths = paths.slice(0, MAX_RECENT_PATHS);

    // Save to new centralized location
    const recentPathsFile = StoragePaths.global.recentPaths();
    ensureStorageDir(StoragePaths.global.config());
    writeFileSync(recentPathsFile, JSON.stringify({ paths }, null, 2), 'utf8');
  } catch {
    // Ignore errors
  }
}

/**
 * Clear recent paths
 */
export function clearRecentPaths(): void {
  try {
    const recentPathsFile = StoragePaths.global.recentPaths();
    ensureStorageDir(StoragePaths.global.config());
    writeFileSync(recentPathsFile, JSON.stringify({ paths: [] }, null, 2), 'utf8');
  } catch {
    // Ignore errors
  }
}

/**
 * Remove a specific path from recent paths
 * @param pathToRemove - Path to remove
 * @returns True if removed, false if not found
 */
export function removeRecentPath(pathToRemove: string): boolean {
  try {
    const normalized = normalizePathForDisplay(resolvePath(pathToRemove));
    let paths = getRecentPaths();
    const originalLength = paths.length;

    // Filter out the path to remove
    paths = paths.filter(p => normalizePathForDisplay(p) !== normalized);

    if (paths.length < originalLength) {
      // Save updated list to new centralized location
      const recentPathsFile = StoragePaths.global.recentPaths();
      ensureStorageDir(StoragePaths.global.config());
      writeFileSync(recentPathsFile, JSON.stringify({ paths }, null, 2), 'utf8');
      return true;
    }
    return false;
  } catch {
    return false;
  }
}
