import express from 'express';
import fs from 'fs';
import path from 'path';
import os from 'os';
import crypto from 'crypto';
import multer from 'multer';
import mime from 'mime-types';

const UPLOAD_DIR = path.join(os.tmpdir(), 'codefree-o-uploads');

if (!fs.existsSync(UPLOAD_DIR)) {
  fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}

const storage = multer.diskStorage({
  destination: (_req, _file, cb) => cb(null, UPLOAD_DIR),
  filename: (_req, file, cb) => {
    const originalname = fixMulterFilename(file);
    const ext = path.extname(originalname) || '';
    const uniqueId = crypto.randomUUID();
    cb(null, `${uniqueId}${ext}`);
  },
});

function fixMulterFilename(file) {
  try {
    const buf = Buffer.from(file.originalname, 'latin1');
    const decoded = buf.toString('utf8');
    if (decoded !== file.originalname && !/\ufffd/.test(decoded)) {
      return decoded;
    }
  } catch {}
  return file.originalname;
}

const upload = multer({
  storage,
  limits: { fileSize: 100 * 1024 * 1024 },
});

const router = express.Router();

function validatePathInProject(projectRoot, targetPath) {
  const resolved = path.isAbsolute(targetPath)
    ? path.resolve(targetPath)
    : path.resolve(projectRoot, targetPath);
  const normalizedRoot = path.resolve(projectRoot) + path.sep;
  if (!resolved.startsWith(normalizedRoot)) {
    return { valid: false, error: 'Path must be under project root' };
  }
  return { valid: true, resolved };
}

async function getFileTree(dirPath, maxDepth = 10, currentDepth = 0, showHidden = false) {
  const entries = [];
  if (currentDepth >= maxDepth) return entries;

  let items;
  try {
    items = await fs.promises.readdir(dirPath, { withFileTypes: true });
  } catch (err) {
    return entries;
  }

  for (const item of items) {
    if (!showHidden && item.name.startsWith('.')) continue;
    if (item.name === 'node_modules' || item.name === '.git') continue;

    const fullPath = path.join(dirPath, item.name);
    const entry = {
      name: item.name,
      path: fullPath,
      type: item.isDirectory() ? 'directory' : 'file',
    };

    if (item.isFile()) {
      try {
        const stat = await fs.promises.stat(fullPath);
        entry.size = stat.size;
        entry.lastModified = stat.mtime.toISOString();
      } catch (e) {
        // skip
      }
    }

    entries.push(entry);

    if (item.isDirectory() && currentDepth < maxDepth - 1) {
      try {
        const children = await getFileTree(fullPath, maxDepth, currentDepth + 1, showHidden);
        entry.children = children;
      } catch (e) {
        // skip
      }
    }
  }

  return entries.sort((a, b) => {
    if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
    return a.name.localeCompare(b.name);
  });
}

router.get('/tree', async (req, res) => {
  try {
    const { path: projectPath, depth = '3' } = req.query;
    if (!projectPath) {
      return res.status(400).json({ error: 'Path is required' });
    }

    const resolvedPath = path.resolve(projectPath);
    try {
      await fs.promises.access(resolvedPath);
    } catch (err) {
      return res.status(404).json({ error: 'Project path not found' });
    }

    const files = await getFileTree(resolvedPath, parseInt(String(depth)), 0, false);
    res.json(files);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

router.get('/content', async (req, res) => {
  try {
    const { path: filePath, projectPath } = req.query;
    if (!filePath) {
      return res.status(400).json({ error: 'File path is required' });
    }

    const resolved = path.resolve(String(filePath));
    if (projectPath) {
      const validation = validatePathInProject(String(projectPath), resolved);
      if (!validation.valid) {
        return res.status(403).json({ error: validation.error });
      }
    }

    const content = await fs.promises.readFile(resolved, 'utf8');
    res.json({ content, path: resolved });
  } catch (error) {
    if (error.code === 'ENOENT') {
      res.status(404).json({ error: 'File not found' });
    } else if (error.code === 'EACCES') {
      res.status(403).json({ error: 'Permission denied' });
    } else if (error.code === 'EISDIR') {
      res.status(400).json({ error: 'Path is a directory' });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

router.put('/content', async (req, res) => {
  try {
    const { filePath, content, projectPath } = req.body;
    if (!filePath || content === undefined) {
      return res.status(400).json({ error: 'File path and content are required' });
    }

    const resolved = path.resolve(filePath);
    if (projectPath) {
      const validation = validatePathInProject(projectPath, resolved);
      if (!validation.valid) {
        return res.status(403).json({ error: validation.error });
      }
    }

    await fs.promises.writeFile(resolved, content, 'utf8');
    res.json({ success: true, path: resolved });
  } catch (error) {
    if (error.code === 'ENOENT') {
      res.status(404).json({ error: 'File not found' });
    } else if (error.code === 'EACCES') {
      res.status(403).json({ error: 'Permission denied' });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

router.post('/create', async (req, res) => {
  try {
    const { parentPath, name, type } = req.body;
    if (!name || !type) {
      return res.status(400).json({ error: 'Name and type are required' });
    }

    const targetPath = path.join(parentPath || '', name);
    const resolved = path.resolve(targetPath);

    try {
      await fs.promises.access(resolved);
      return res.status(409).json({ error: 'Already exists' });
    } catch {
      // doesn't exist, good
    }

    if (type === 'directory') {
      await fs.promises.mkdir(resolved, { recursive: true });
    } else {
      const parentDir = path.dirname(resolved);
      await fs.promises.mkdir(parentDir, { recursive: true });
      await fs.promises.writeFile(resolved, '', 'utf8');
    }

    res.json({ success: true, path: resolved, name, type });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

router.delete('/', async (req, res) => {
  try {
    const { filePath } = req.body;
    if (!filePath) {
      return res.status(400).json({ error: 'File path is required' });
    }

    const resolved = path.resolve(filePath);
    const stat = await fs.promises.stat(resolved);

    if (stat.isDirectory()) {
      await fs.promises.rm(resolved, { recursive: true, force: true });
    } else {
      await fs.promises.unlink(resolved);
    }

    res.json({ success: true, path: resolved });
  } catch (error) {
    if (error.code === 'ENOENT') {
      res.status(404).json({ error: 'Not found' });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

router.get('/browse', async (req, res) => {
  try {
    const { path: dirPath } = req.query;
    const targetPath = dirPath ? path.resolve(String(dirPath)) : '';

    if (!targetPath) {
      if (process.platform === 'win32') {
        const drives = [];
        for (let code = 65; code <= 90; code++) {
          const drive = String.fromCharCode(code) + ':\\';
          try {
            await fs.promises.access(drive);
            drives.push({ name: drive, path: drive, type: 'directory' });
          } catch {}
        }
        return res.json({ currentPath: '', parentPath: null, directories: drives });
      }
      const homeDir = process.env.HOME || process.env.USERPROFILE || '/';
      const items = await listDirectoriesOnly(homeDir);
      return res.json({ currentPath: homeDir, parentPath: path.dirname(homeDir), directories: items });
    }

    try {
      await fs.promises.access(targetPath);
    } catch {
      return res.status(404).json({ error: 'Directory not found' });
    }

    const stat = await fs.promises.stat(targetPath);
    if (!stat.isDirectory()) {
      return res.status(400).json({ error: 'Path is not a directory' });
    }

    const directories = await listDirectoriesOnly(targetPath);
    const parentPath = path.dirname(targetPath);

    const isDriveRoot = /^[A-Za-z]:\\?$/.test(targetPath);

    res.json({
      currentPath: targetPath,
      parentPath: isDriveRoot ? '' : (targetPath === parentPath ? null : parentPath),
      directories,
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

async function listDirectoriesOnly(dirPath) {
  let items;
  try {
    items = await fs.promises.readdir(dirPath, { withFileTypes: true });
  } catch {
    return [];
  }

  const directories = [];
  for (const item of items) {
    if (!item.isDirectory()) continue;
    if (item.name.startsWith('.') && process.platform !== 'win32') continue;
    const fullPath = path.join(dirPath, item.name);
    try {
      await fs.promises.access(fullPath, fs.constants.R_OK);
      directories.push({ name: item.name, path: fullPath, type: 'directory' });
    } catch {}
  }

  return directories.sort((a, b) => a.name.localeCompare(b.name));
}

router.post('/upload', upload.array('files', 20), async (req, res) => {
  try {
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ error: 'No files uploaded' });
    }

    const results = await Promise.all(req.files.map(async (file) => {
      const filePath = path.resolve(file.path);
      const originalname = fixMulterFilename(file);
      const mimeType = file.mimetype || mime.lookup(originalname) || 'application/octet-stream';

      const fileBuffer = await fs.promises.readFile(filePath);
      const base64Data = fileBuffer.toString('base64');
      const dataUrl = `data:${mimeType};base64,${base64Data}`;

      return {
        filename: originalname,
        mime: mimeType,
        url: dataUrl,
        size: file.size,
        source: {
          type: 'file',
          path: filePath,
          filename: originalname,
          mime: mimeType,
          text: {
            value: `[Image]`,
            start: 0,
            end: 7,
          },
        },
      };
    }));

    res.json({ files: results });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);

const SKIP_DIRS = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.turbo', 'coverage', '__pycache__']);

async function listSourceFiles(dirPath, basePath) {
  const results = [];
  let items;
  try {
    items = await fs.promises.readdir(dirPath, { withFileTypes: true });
  } catch {
    return results;
  }

  for (const item of items) {
    if (item.name.startsWith('.')) continue;
    if (item.isDirectory() && SKIP_DIRS.has(item.name)) continue;

    const fullPath = path.join(dirPath, item.name);
    const relativePath = path.relative(basePath, fullPath);

    if (item.isFile()) {
      const ext = path.extname(item.name).toLowerCase();
      if (SOURCE_EXTENSIONS.has(ext)) {
        try {
          const stat = await fs.promises.stat(fullPath);
          results.push({ path: fullPath, size: stat.size });
        } catch {}
      }
    } else if (item.isDirectory()) {
      const children = await listSourceFiles(fullPath, basePath);
      results.push(...children);
    }
  }

  return results;
}

router.get('/list', async (req, res) => {
  try {
    const { path: projectPath } = req.query;
    if (!projectPath) {
      return res.status(400).json({ error: 'Project path is required' });
    }

    const resolvedPath = path.resolve(projectPath);
    try {
      await fs.promises.access(resolvedPath);
    } catch {
      return res.status(404).json({ error: 'Project path not found' });
    }

    const files = await listSourceFiles(resolvedPath, resolvedPath);
    res.json({ rootPath: resolvedPath, files });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

router.get('/deps', async (req, res) => {
  try {
    const { path: projectPath } = req.query;
    if (!projectPath) {
      return res.status(400).json({ error: 'Project path is required' });
    }

    const resolvedPath = path.resolve(projectPath);
    const pkgPath = path.join(resolvedPath, 'package.json');

    try {
      await fs.promises.access(pkgPath);
    } catch {
      return res.json({ dependencies: {}, devDependencies: {} });
    }

    const pkgContent = await fs.promises.readFile(pkgPath, 'utf8');
    const pkg = JSON.parse(pkgContent);

    res.json({
      dependencies: pkg.dependencies || {},
      devDependencies: pkg.devDependencies || {},
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

export { router as filesRouter };
