import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import os from 'os';
import { addProjectManually } from '../projects.js';

const router = express.Router();

function sanitizeGitError(message, token) {
  if (!message || !token) return message;
  return message.replace(new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '***');
}

// Configure allowed workspace root.
// In platform mode (Railway), file operations are proxied to user's local machine via relay,
// so no server-side path restriction is needed. Only restrict in self-hosted mode if explicitly set.
const IS_LOCAL = !process.env.RAILWAY_ENVIRONMENT && !process.env.VERCEL && !process.env.RENDER;
const IS_PLATFORM = !!process.env.RAILWAY_ENVIRONMENT || !!process.env.VERCEL;
export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || (IS_LOCAL || IS_PLATFORM ? null : os.homedir());

// System-critical paths that should never be used as workspace directories
export const FORBIDDEN_PATHS = [
  // Unix
  '/',
  '/etc',
  '/bin',
  '/sbin',
  '/usr',
  '/dev',
  '/proc',
  '/sys',
  '/var',
  '/boot',
  '/root',
  '/lib',
  '/lib64',
  '/opt',
  '/tmp',
  '/run',
  // Windows
  'C:\\Windows',
  'C:\\Program Files',
  'C:\\Program Files (x86)',
  'C:\\ProgramData',
  'C:\\System Volume Information',
  'C:\\$Recycle.Bin'
];

/**
 * Validates that a path is safe for workspace operations
 * @param {string} requestedPath - The path to validate
 * @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
 */
export async function validateWorkspacePath(requestedPath) {
  try {
    // Resolve to absolute path
    let absolutePath = path.resolve(requestedPath);

    // Check if path is a forbidden system directory
    const normalizedPath = path.normalize(absolutePath);
    if (FORBIDDEN_PATHS.includes(normalizedPath) || normalizedPath === '/') {
      return {
        valid: false,
        error: 'Cannot use system-critical directories as workspace locations'
      };
    }

    // Additional check for paths starting with forbidden directories
    for (const forbidden of FORBIDDEN_PATHS) {
      if (normalizedPath === forbidden ||
          normalizedPath.startsWith(forbidden + path.sep)) {
        // Exception: /var/tmp and similar user-accessible paths might be allowed
        // but /var itself and most /var subdirectories should be blocked
        if (forbidden === '/var' &&
            (normalizedPath.startsWith('/var/tmp') ||
             normalizedPath.startsWith('/var/folders'))) {
          continue; // Allow these specific cases
        }

        return {
          valid: false,
          error: `Cannot create workspace in system directory: ${forbidden}`
        };
      }
    }

    // Try to resolve the real path (following symlinks)
    let realPath;
    try {
      // Check if path exists to resolve real path
      await fs.access(absolutePath);
      realPath = await fs.realpath(absolutePath);
    } catch (error) {
      if (error.code === 'ENOENT') {
        // Path doesn't exist yet - check parent directory
        let parentPath = path.dirname(absolutePath);
        try {
          const parentRealPath = await fs.realpath(parentPath);

          // Reconstruct the full path with real parent
          realPath = path.join(parentRealPath, path.basename(absolutePath));
        } catch (parentError) {
          if (parentError.code === 'ENOENT') {
            // Parent doesn't exist either - use the absolute path as-is
            // We'll validate it's within allowed root
            realPath = absolutePath;
          } else {
            throw parentError;
          }
        }
      } else {
        throw error;
      }
    }

    // If a workspace root is configured, enforce containment
    if (WORKSPACES_ROOT) {
      const resolvedWorkspaceRoot = await fs.realpath(WORKSPACES_ROOT);

      if (!realPath.startsWith(resolvedWorkspaceRoot + path.sep) &&
          realPath !== resolvedWorkspaceRoot) {
        return {
          valid: false,
          error: `Workspace path must be within the allowed workspace root: ${WORKSPACES_ROOT}`
        };
      }

      // Additional symlink check for existing paths
      try {
        await fs.access(absolutePath);
        const stats = await fs.lstat(absolutePath);

        if (stats.isSymbolicLink()) {
          const linkTarget = await fs.readlink(absolutePath);
          const resolvedTarget = path.resolve(path.dirname(absolutePath), linkTarget);
          const realTarget = await fs.realpath(resolvedTarget);

          if (!realTarget.startsWith(resolvedWorkspaceRoot + path.sep) &&
              realTarget !== resolvedWorkspaceRoot) {
            return {
              valid: false,
              error: 'Symlink target is outside the allowed workspace root'
            };
          }
        }
      } catch (error) {
        if (error.code !== 'ENOENT') {
          throw error;
        }
      }
    }

    return {
      valid: true,
      resolvedPath: realPath
    };

  } catch (error) {
    return {
      valid: false,
      error: 'Path validation failed'
    };
  }
}

/**
 * Create a new workspace
 * POST /api/projects/create-workspace
 *
 * Body:
 * - workspaceType: 'existing' | 'new'
 * - path: string (workspace path)
 * - githubUrl?: string (optional, for new workspaces)
 * - githubTokenId?: number (optional, ID of stored token)
 * - newGithubToken?: string (optional, one-time token)
 */
router.post('/create-workspace', async (req, res) => {
  try {
    const { workspaceType, path: workspacePath, githubUrl, githubTokenId, newGithubToken } = req.body;

    // Validate required fields
    if (!workspaceType || !workspacePath) {
      return res.status(400).json({ error: 'workspaceType and path are required' });
    }

    if (!['existing', 'new'].includes(workspaceType)) {
      return res.status(400).json({ error: 'workspaceType must be "existing" or "new"' });
    }

    // Handle existing workspace
    if (workspaceType === 'existing') {
      // In cloud mode (Railway/Vercel/Render), the path is on the user's local machine.
      // We cannot validate it server-side. Just trust the user's input and add it.
      const IS_CLOUD_ENV = !!(process.env.RAILWAY_ENVIRONMENT || process.env.VERCEL || process.env.RENDER);
      if (IS_CLOUD_ENV) {
        const project = await addProjectManually(workspacePath);
        return res.json({ success: true, project, message: 'Workspace added successfully' });
      }

      // Local/self-hosted mode: validate on server filesystem
      const validation = await validateWorkspacePath(workspacePath);
      if (!validation.valid) {
        return res.status(400).json({ error: 'Invalid workspace path', details: validation.error });
      }
      const absolutePath = validation.resolvedPath;

      try {
        await fs.access(absolutePath);
        const stats = await fs.stat(absolutePath);

        if (!stats.isDirectory()) {
          return res.status(400).json({ error: 'Path exists but is not a directory' });
        }
      } catch (error) {
        if (error.code === 'ENOENT') {
          return res.status(404).json({ error: 'Workspace path does not exist' });
        }
        throw error;
      }

      const project = await addProjectManually(absolutePath);

      return res.json({
        success: true,
        project,
        message: 'Existing workspace added successfully'
      });
    }

    // Handle new workspace creation
    if (workspaceType === 'new') {
      const IS_CLOUD_ENV = !!(process.env.RAILWAY_ENVIRONMENT || process.env.VERCEL || process.env.RENDER);

      // In cloud mode, create directory and clone via relay
      if (IS_CLOUD_ENV) {
        if (!req.hasRelay || !req.hasRelay()) {
          return res.status(503).json({
            error: 'Machine not connected',
            message: 'Run "uc connect" on your local machine to create new workspaces.',
            code: 'RELAY_NOT_CONNECTED'
          });
        }

        // Create directory via relay
        await req.sendRelay('create-folder', { folderPath: workspacePath }, 15000);

        if (githubUrl) {
          let githubToken = null;
          if (githubTokenId) {
            const token = await getGithubTokenById(githubTokenId, req.user.id);
            if (!token) {
              return res.status(404).json({ error: 'GitHub token not found' });
            }
            githubToken = token.github_token;
          } else if (newGithubToken) {
            githubToken = newGithubToken;
          }

          const normalizedUrl = githubUrl.replace(/\/+$/, '').replace(/\.git$/, '');
          const repoName = normalizedUrl.split('/').pop() || 'repository';
          // Build clone URL with token if needed
          let cloneUrl = githubUrl;
          if (githubToken) {
            const urlObj = new URL(githubUrl.endsWith('.git') ? githubUrl : `${githubUrl}.git`);
            urlObj.username = githubToken;
            urlObj.password = 'x-oauth-basic';
            cloneUrl = urlObj.toString();
          }
          const clonePath = `${workspacePath}/${repoName}`;

          try {
            await req.sendRelay('shell-command', {
              command: `git clone "${cloneUrl}" "${clonePath}"`,
              cwd: workspacePath
            }, 120000);
          } catch (error) {
            const sanitized = sanitizeGitError(error.message, githubToken);
            throw new Error(`Failed to clone repository: ${sanitized}`);
          }

          const project = await addProjectManually(clonePath);
          return res.json({ success: true, project, message: 'New workspace created and repository cloned successfully' });
        }

        const project = await addProjectManually(workspacePath);
        return res.json({ success: true, project, message: 'New workspace created successfully' });
      }

      // Local mode
      const validation = await validateWorkspacePath(workspacePath);
      const absolutePath = validation.valid ? validation.resolvedPath : path.resolve(workspacePath);
      await fs.mkdir(absolutePath, { recursive: true });

      if (githubUrl) {
        let githubToken = null;

        if (githubTokenId) {
          const token = await getGithubTokenById(githubTokenId, req.user.id);
          if (!token) {
            await fs.rm(absolutePath, { recursive: true, force: true });
            return res.status(404).json({ error: 'GitHub token not found' });
          }
          githubToken = token.github_token;
        } else if (newGithubToken) {
          githubToken = newGithubToken;
        }

        const normalizedUrl = githubUrl.replace(/\/+$/, '').replace(/\.git$/, '');
        const repoName = normalizedUrl.split('/').pop() || 'repository';
        const clonePath = path.join(absolutePath, repoName);

        try {
          await fs.access(clonePath);
          return res.status(409).json({
            error: 'Directory already exists',
            details: `The destination path "${clonePath}" already exists. Please choose a different location or remove the existing directory.`
          });
        } catch (err) {
          // Directory doesn't exist, which is what we want
        }

        try {
          await cloneGitHubRepository(githubUrl, clonePath, githubToken);
        } catch (error) {
          try {
            const stats = await fs.stat(clonePath);
            if (stats.isDirectory()) {
              await fs.rm(clonePath, { recursive: true, force: true });
            }
          } catch (cleanupError) {
            // ignore
          }
          throw new Error(`Failed to clone repository: ${error.message}`);
        }

        const project = await addProjectManually(clonePath);
        return res.json({ success: true, project, message: 'New workspace created and repository cloned successfully' });
      }

      const project = await addProjectManually(absolutePath);
      return res.json({ success: true, project, message: 'New workspace created successfully' });
    }

  } catch (error) {
    // workspace creation error
    res.status(500).json({
      error: 'Failed to create workspace',
      details: error.message
    });
  }
});

/**
 * Helper function to get GitHub token from database
 */
async function getGithubTokenById(tokenId, userId) {
  const { getDatabase } = await import('../database/db.js');
  const db = await getDatabase();

  const credential = await db.get(
    'SELECT * FROM user_credentials WHERE id = ? AND user_id = ? AND credential_type = ? AND is_active = 1',
    [tokenId, userId, 'github_token']
  );

  // Return in the expected format (github_token field for compatibility)
  if (credential) {
    return {
      ...credential,
      github_token: credential.credential_value
    };
  }

  return null;
}

/**
 * Clone repository with progress streaming (SSE)
 * GET /api/projects/clone-progress
 */
router.get('/clone-progress', async (req, res) => {
  const { path: workspacePath, githubUrl, githubTokenId, newGithubToken } = req.query;

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();

  const sendEvent = (type, data) => {
    res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
  };

  try {
    if (!workspacePath || !githubUrl) {
      sendEvent('error', { message: 'workspacePath and githubUrl are required' });
      res.end();
      return;
    }

    const IS_CLOUD_ENV = !!(process.env.RAILWAY_ENVIRONMENT || process.env.VERCEL || process.env.RENDER);

    // Cloud mode: clone via relay on user's machine
    if (IS_CLOUD_ENV) {
      if (!req.hasRelay || !req.hasRelay()) {
        sendEvent('error', { message: 'Machine not connected. Run "uc connect" on your local machine.' });
        res.end();
        return;
      }

      let githubToken = null;
      if (githubTokenId) {
        const token = await getGithubTokenById(parseInt(githubTokenId), req.user.id);
        if (!token) {
          sendEvent('error', { message: 'GitHub token not found' });
          res.end();
          return;
        }
        githubToken = token.github_token;
      } else if (newGithubToken) {
        githubToken = newGithubToken;
      }

      sendEvent('progress', { message: 'Creating directory...' });
      await req.sendRelay('create-folder', { folderPath: workspacePath }, 15000);

      const normalizedUrl = githubUrl.replace(/\/+$/, '').replace(/\.git$/, '');
      const repoName = normalizedUrl.split('/').pop() || 'repository';
      const clonePath = `${workspacePath}/${repoName}`;

      let cloneUrl = githubUrl;
      if (githubToken) {
        try {
          const url = new URL(githubUrl.endsWith('.git') ? githubUrl : `${githubUrl}.git`);
          url.username = githubToken;
          url.password = 'x-oauth-basic';
          cloneUrl = url.toString();
        } catch (error) {
          // SSH URL or invalid - use as-is
        }
      }

      sendEvent('progress', { message: `Cloning into '${repoName}'...` });

      try {
        await req.sendRelay('shell-command', {
          command: `git clone "${cloneUrl}" "${clonePath}"`,
          cwd: workspacePath
        }, 120000);

        const project = await addProjectManually(clonePath);
        sendEvent('complete', { project, message: 'Repository cloned successfully' });
      } catch (error) {
        const sanitized = sanitizeGitError(error.message, githubToken);
        sendEvent('error', { message: sanitized || 'Git clone failed' });
      }

      res.end();
      return;
    }

    // Local mode
    const validation = await validateWorkspacePath(workspacePath);
    if (!validation.valid) {
      sendEvent('error', { message: validation.error });
      res.end();
      return;
    }

    const absolutePath = validation.resolvedPath;

    await fs.mkdir(absolutePath, { recursive: true });

    let githubToken = null;
    if (githubTokenId) {
      const token = await getGithubTokenById(parseInt(githubTokenId), req.user.id);
      if (!token) {
        await fs.rm(absolutePath, { recursive: true, force: true });
        sendEvent('error', { message: 'GitHub token not found' });
        res.end();
        return;
      }
      githubToken = token.github_token;
    } else if (newGithubToken) {
      githubToken = newGithubToken;
    }

    const normalizedUrl = githubUrl.replace(/\/+$/, '').replace(/\.git$/, '');
    const repoName = normalizedUrl.split('/').pop() || 'repository';
    const clonePath = path.join(absolutePath, repoName);

    try {
      await fs.access(clonePath);
      sendEvent('error', { message: `Directory "${repoName}" already exists. Please choose a different location or remove the existing directory.` });
      res.end();
      return;
    } catch (err) {
      // Directory doesn't exist, which is what we want
    }

    let cloneUrl = githubUrl;
    if (githubToken) {
      try {
        const url = new URL(githubUrl);
        url.username = githubToken;
        url.password = '';
        cloneUrl = url.toString();
      } catch (error) {
        // SSH URL or invalid - use as-is
      }
    }

    sendEvent('progress', { message: `Cloning into '${repoName}'...` });

    const gitProcess = spawn('git', ['clone', '--progress', cloneUrl, clonePath], {
      stdio: ['ignore', 'pipe', 'pipe'],
      env: {
        ...process.env,
        GIT_TERMINAL_PROMPT: '0'
      }
    });

    let lastError = '';

    gitProcess.stdout.on('data', (data) => {
      const message = data.toString().trim();
      if (message) {
        sendEvent('progress', { message });
      }
    });

    gitProcess.stderr.on('data', (data) => {
      const message = data.toString().trim();
      lastError = message;
      if (message) {
        sendEvent('progress', { message });
      }
    });

    gitProcess.on('close', async (code) => {
      if (code === 0) {
        try {
          const project = await addProjectManually(clonePath);
          sendEvent('complete', { project, message: 'Repository cloned successfully' });
        } catch (error) {
          sendEvent('error', { message: `Clone succeeded but failed to add project: ${error.message}` });
        }
      } else {
        const sanitizedError = sanitizeGitError(lastError, githubToken);
        let errorMessage = 'Git clone failed';
        if (lastError.includes('Authentication failed') || lastError.includes('could not read Username')) {
          errorMessage = 'Authentication failed. Please check your credentials.';
        } else if (lastError.includes('Repository not found')) {
          errorMessage = 'Repository not found. Please check the URL and ensure you have access.';
        } else if (lastError.includes('already exists')) {
          errorMessage = 'Directory already exists';
        } else if (sanitizedError) {
          errorMessage = sanitizedError;
        }
        try {
          await fs.rm(clonePath, { recursive: true, force: true });
        } catch (cleanupError) {
          // cleanup failure ignored
        }
        sendEvent('error', { message: errorMessage });
      }
      res.end();
    });

    gitProcess.on('error', (error) => {
      if (error.code === 'ENOENT') {
        sendEvent('error', { message: 'Git is not installed or not in PATH' });
      } else {
        sendEvent('error', { message: error.message });
      }
      res.end();
    });

    req.on('close', () => {
      gitProcess.kill();
    });

  } catch (error) {
    sendEvent('error', { message: error.message });
    res.end();
  }
});

/**
 * Helper function to clone a GitHub repository
 */
function cloneGitHubRepository(githubUrl, destinationPath, githubToken = null) {
  return new Promise((resolve, reject) => {
    let cloneUrl = githubUrl;

    if (githubToken) {
      try {
        const url = new URL(githubUrl);
        url.username = githubToken;
        url.password = '';
        cloneUrl = url.toString();
      } catch (error) {
        // SSH URL - use as-is
      }
    }

    const gitProcess = spawn('git', ['clone', '--progress', cloneUrl, destinationPath], {
      stdio: ['ignore', 'pipe', 'pipe'],
      env: {
        ...process.env,
        GIT_TERMINAL_PROMPT: '0'
      }
    });

    let stdout = '';
    let stderr = '';

    gitProcess.stdout.on('data', (data) => {
      stdout += data.toString();
    });

    gitProcess.stderr.on('data', (data) => {
      stderr += data.toString();
    });

    gitProcess.on('close', (code) => {
      if (code === 0) {
        resolve({ stdout, stderr });
      } else {
        let errorMessage = 'Git clone failed';

        if (stderr.includes('Authentication failed') || stderr.includes('could not read Username')) {
          errorMessage = 'Authentication failed. Please check your GitHub token.';
        } else if (stderr.includes('Repository not found')) {
          errorMessage = 'Repository not found. Please check the URL and ensure you have access.';
        } else if (stderr.includes('already exists')) {
          errorMessage = 'Directory already exists';
        } else if (stderr) {
          errorMessage = stderr;
        }

        reject(new Error(errorMessage));
      }
    });

    gitProcess.on('error', (error) => {
      if (error.code === 'ENOENT') {
        reject(new Error('Git is not installed or not in PATH'));
      } else {
        reject(error);
      }
    });
  });
}

export default router;
