import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import multer from 'multer';
import AdmZip from 'adm-zip';
import { getUserPaths, getPublicPaths, DATA_DIR } from '../services/user-directories.js';
import { ensureSystemRepo, SYSTEM_REPO_OWNER, SYSTEM_REPO_NAME, SYSTEM_REPO_URL } from '../services/system-repo.js';
import { loadProjectConfig, extractProjectDirectory } from '../projects.js';

const router = express.Router();

// Skill name validation: letters, numbers, hyphens, underscores
const SKILL_NAME_REGEX = /^[a-zA-Z0-9_-]{1,100}$/;

// Trusted git hosting domains
const TRUSTED_GIT_HOSTS = ['github.com', 'gitlab.com', 'bitbucket.org', 'git.amberweather.com'];

// Configure multer for zip file uploads
const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 50 * 1024 * 1024 }, // 50MB limit
  fileFilter: (req, file, cb) => {
    if (file.mimetype === 'application/zip' || file.originalname.endsWith('.zip')) {
      cb(null, true);
    } else {
      cb(new Error('Only ZIP files are allowed'));
    }
  }
});

/**
 * Parse skill metadata from SKILL.md YAML frontmatter
 */
async function parseSkillMetadata(skillPath) {
  try {
    const skillsFile = path.join(skillPath, 'SKILL.md');
    const content = await fs.readFile(skillsFile, 'utf-8');

    let title = path.basename(skillPath);
    let description = '';

    // Parse YAML frontmatter (between --- delimiters)
    const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
    if (fmMatch) {
      const fm = fmMatch[1];
      const nameMatch = fm.match(/^name:\s*(.+)$/m);
      if (nameMatch) title = nameMatch[1].trim();

      // Handle YAML block scalars: `description: >` (folded) or `description: |` (literal)
      const descBlockMatch = fm.match(/^description:\s*[>|]\s*\n((?:[ \t]+[^\n]*\n?)*)/m);
      if (descBlockMatch) {
        description = descBlockMatch[1]
          .split('\n')
          .map(line => line.trimStart())
          .filter(line => line.length > 0)
          .join(' ')
          .trim();
      } else {
        const descMatch = fm.match(/^description:\s*(.+)$/m);
        if (descMatch) description = descMatch[1].trim();
      }
    }

    return { title, description };
  } catch {
    return { title: path.basename(skillPath), description: '' };
  }
}

/**
 * Check if a path is a valid skill directory (must contain SKILL.md)
 */
async function isValidSkill(skillPath) {
  try {
    const stat = await fs.stat(skillPath);
    if (!stat.isDirectory()) return false;

    await fs.access(path.join(skillPath, 'SKILL.md'));
    return true;
  } catch {
    return false;
  }
}

/**
 * Recursively scan a directory for valid skill subdirectories.
 * Supports nested structures like skills/XXX (maxDepth controls recursion depth).
 * If a directory is itself a valid skill it is collected and not recursed into.
 */
async function scanDirForSkills(dirPath, repository, installedSkills, maxDepth = 2) {
  const found = [];

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

  for (const entry of entries) {
    if (entry.name.startsWith('.') ||
        entry.name.toLowerCase().startsWith('readme') ||
        !entry.isDirectory()) {
      continue;
    }

    const skillPath = path.join(dirPath, entry.name);

    if (await isValidSkill(skillPath)) {
      const metadata = await parseSkillMetadata(skillPath);
      found.push({
        name: entry.name,
        title: metadata.title,
        description: metadata.description,
        repository,
        installed: installedSkills ? installedSkills.has(entry.name) : false,
        path: skillPath
      });
    } else if (maxDepth > 0) {
      // Not a skill itself — recurse into it looking for nested skills
      const nested = await scanDirForSkills(skillPath, repository, installedSkills, maxDepth - 1);
      found.push(...nested);
    }
  }

  return found;
}

/**
 * Check if URL is SSH format (git@host:path)
 */
function isSshUrl(url) {
  return /^[a-zA-Z0-9_-]+@[a-zA-Z0-9.-]+:.+$/.test(url);
}

/**
 * Parse SSH URL format (git@host:owner/repo.git)
 */
function parseSshUrl(url) {
  const match = url.match(/^[a-zA-Z0-9_-]+@([a-zA-Z0-9.-]+):(.+)$/);
  if (!match) return null;

  const host = match[1].toLowerCase();
  const pathPart = match[2].replace(/\.git$/, '');
  const pathParts = pathPart.split('/');

  if (pathParts.length >= 2) {
    return { host, owner: pathParts[0], repo: pathParts[1] };
  }
  return { host, owner: null, repo: null };
}

/**
 * Validate GitHub URL
 */
function validateGitUrl(url) {
  if (!url || typeof url !== 'string') {
    return { valid: false, error: 'Repository URL is required' };
  }

  // Handle SSH format: git@host:owner/repo.git
  if (isSshUrl(url)) {
    const parsed = parseSshUrl(url);
    if (!parsed) {
      return { valid: false, error: 'Invalid SSH URL format' };
    }

    if (!TRUSTED_GIT_HOSTS.includes(parsed.host)) {
      return {
        valid: false,
        error: `Only trusted git hosts are allowed: ${TRUSTED_GIT_HOSTS.join(', ')}`
      };
    }

    return { valid: true, isSsh: true };
  }

  let parsedUrl;
  try {
    parsedUrl = new URL(url);
  } catch {
    return { valid: false, error: 'Invalid URL format' };
  }

  const host = parsedUrl.hostname.toLowerCase();
  const allowedProtocols = ['https:', 'http:'];

  // Allow http protocol only for git.amberweather.com
  if (host === 'git.amberweather.com') {
    if (!allowedProtocols.includes(parsedUrl.protocol)) {
      return { valid: false, error: 'Only HTTPS or HTTP URLs are allowed for this host' };
    }
  } else {
    if (parsedUrl.protocol !== 'https:') {
      return { valid: false, error: 'Only HTTPS URLs are allowed' };
    }
  }

  if (!TRUSTED_GIT_HOSTS.includes(host)) {
    return {
      valid: false,
      error: `Only trusted git hosts are allowed: ${TRUSTED_GIT_HOSTS.join(', ')}`
    };
  }

  return { valid: true };
}

/**
 * Extract owner and repo from git URL
 */
function parseGitUrl(url) {
  // Handle SSH format
  if (isSshUrl(url)) {
    const parsed = parseSshUrl(url);
    if (parsed && parsed.owner && parsed.repo) {
      return { owner: parsed.owner, repo: parsed.repo };
    }
    return null;
  }

  const parsedUrl = new URL(url);
  const pathParts = parsedUrl.pathname.replace(/^\//, '').replace(/\.git$/, '').split('/');
  if (pathParts.length >= 2) {
    return { owner: pathParts[0], repo: pathParts[1] };
  }
  return null;
}

/**
 * Clone a git repository
 */
function cloneRepository(url, destinationPath) {
  return new Promise((resolve, reject) => {
    const gitProcess = spawn('git', ['clone', '--depth', '1', url, destinationPath], {
      stdio: ['ignore', 'pipe', 'pipe'],
      env: {
        ...process.env,
        GIT_TERMINAL_PROMPT: '0'
      }
    });

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

    gitProcess.on('close', (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(new Error(stderr || `Git clone failed with code ${code}`));
      }
    });

    gitProcess.on('error', (err) => {
      reject(err);
    });
  });
}

/**
 * Update a git repository
 */
function updateRepository(repoPath) {
  return new Promise((resolve, reject) => {
    const gitProcess = spawn('git', ['pull', '--ff-only'], {
      cwd: repoPath,
      stdio: ['ignore', 'pipe', 'pipe'],
      env: {
        ...process.env,
        GIT_TERMINAL_PROMPT: '0'
      }
    });

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

    gitProcess.on('close', (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(new Error(stderr || `Git pull failed with code ${code}`));
      }
    });

    gitProcess.on('error', (err) => {
      reject(err);
    });
  });
}

/**
 * Run a git command and return a promise
 */
function runGit(args, cwd) {
  return new Promise((resolve, reject) => {
    const opts = { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } };
    if (cwd) opts.cwd = cwd;
    const proc = spawn('git', args, opts);
    let stderr = '';
    proc.stderr.on('data', d => { stderr += d.toString(); });
    proc.on('close', code => {
      if (code === 0) resolve();
      else reject(new Error(stderr || `git ${args[0]} failed with code ${code}`));
    });
    proc.on('error', err => reject(err));
  });
}

/**
 * Run a git command and return stdout as a string
 */
function runGitOutput(args, cwd) {
  return new Promise((resolve, reject) => {
    const opts = { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } };
    if (cwd) opts.cwd = cwd;
    const proc = spawn('git', args, opts);
    let stdout = '';
    let stderr = '';
    proc.stdout.on('data', d => { stdout += d.toString(); });
    proc.stderr.on('data', d => { stderr += d.toString(); });
    proc.on('close', code => {
      if (code === 0) resolve(stdout);
      else reject(new Error(stderr || `git ${args[0]} failed with code ${code}`));
    });
    proc.on('error', err => reject(err));
  });
}

/**
 * Recursively copy all files and directories from src to dst.
 */
async function copyDirRecursive(src, dst) {
  await fs.mkdir(dst, { recursive: true });
  const entries = await fs.readdir(src, { withFileTypes: true });
  for (const entry of entries) {
    const srcPath = path.join(src, entry.name);
    const dstPath = path.join(dst, entry.name);
    if (entry.isDirectory()) {
      await copyDirRecursive(srcPath, dstPath);
    } else {
      await fs.copyFile(srcPath, dstPath);
    }
  }
}

/**
 * Find a skill directory in the system repo (supports direct and nested structures).
 * Returns absolute path to skill directory, or null if not found.
 */
async function findSkillInSystemRepo(systemRepoPath, skillName) {
  // Direct: {systemRepoPath}/{skillName}/SKILL.md
  const directPath = path.join(systemRepoPath, skillName);
  try {
    await fs.access(path.join(directPath, 'SKILL.md'));
    return directPath;
  } catch {}

  // Nested: {systemRepoPath}/skills/{skillName}/SKILL.md
  const nestedPath = path.join(systemRepoPath, 'skills', skillName);
  try {
    await fs.access(path.join(nestedPath, 'SKILL.md'));
    return nestedPath;
  } catch {}

  return null;
}

/**
 * GET /api/skills
 * List user's installed skills
 */
router.get('/', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const userPaths = getUserPaths(userUuid);

    // Ensure directory exists
    await fs.mkdir(userPaths.skillsDir, { recursive: true });

    const entries = await fs.readdir(userPaths.skillsDir, { withFileTypes: true });
    const skills = [];

    for (const entry of entries) {
      // Skip hidden files and READMEs
      if (entry.name.startsWith('.') || entry.name.toLowerCase().startsWith('readme')) {
        continue;
      }

      const skillPath = path.join(userPaths.skillsDir, entry.name);
      let realPath = skillPath;
      let isSymlink = false;
      let source = 'unknown';
      let repository = null;

      try {
        const stat = await fs.lstat(skillPath);
        isSymlink = stat.isSymbolicLink();

        if (isSymlink) {
          try {
            realPath = await fs.realpath(skillPath);
          } catch (realpathErr) {
            if (realpathErr.code === 'ENOENT') {
              // Dangling symlink - target no longer exists, remove it
              await fs.unlink(skillPath).catch(() => {});
              console.log(`[Skills] Removed dangling symlink: ${entry.name}`);
            }
            continue;
          }

          // Determine source based on realPath
          if (realPath.includes('/skills-import/')) {
            source = 'imported';
          } else if (realPath.includes('/skills-repo/')) {
            source = 'repo';
            // Extract repository info from path
            const repoMatch = realPath.match(/skills-repo\/([^/]+)\/([^/]+)/);
            if (repoMatch) {
              repository = `${repoMatch[1]}/${repoMatch[2]}`;
            }
          }
        }

        // Check if it's a valid skill
        if (!await isValidSkill(realPath)) {
          continue;
        }

        const metadata = await parseSkillMetadata(realPath);

        skills.push({
          name: entry.name,
          title: metadata.title,
          description: metadata.description,
          enabled: true,
          source,
          repository,
          path: realPath,
          scope: 'user'
        });
      } catch (err) {
        console.error(`Error reading skill ${entry.name}:`, err.message);
      }
    }

    // Also scan project-scoped skills from each project's .claude/skills/
    try {
      const projectConfig = await loadProjectConfig(userUuid);
      for (const projectName of Object.keys(projectConfig)) {
        let projectDir = null;
        try { projectDir = await extractProjectDirectory(projectName, userUuid); } catch { continue; }
        if (!projectDir) continue;
        const projectSkillsDir = path.join(projectDir, '.claude', 'skills');
        let projectEntries;
        try { projectEntries = await fs.readdir(projectSkillsDir, { withFileTypes: true }); } catch { continue; }
        for (const entry of projectEntries) {
          if (entry.name.startsWith('.') || entry.name.toLowerCase().startsWith('readme')) continue;
          const skillPath = path.join(projectSkillsDir, entry.name);
          let realPath = skillPath;
          let source = 'repo';
          let repository = null;
          try {
            const stat = await fs.lstat(skillPath);
            if (stat.isSymbolicLink()) {
              try { realPath = await fs.realpath(skillPath); }
              catch (e) { if (e.code === 'ENOENT') await fs.unlink(skillPath).catch(() => {}); continue; }
              if (realPath.includes('/skills-import/')) source = 'imported';
              else if (realPath.includes('/skills-repo/')) {
                source = 'repo';
                const repoMatch = realPath.match(/skills-repo\/([^/]+)\/([^/]+)/);
                if (repoMatch) repository = `${repoMatch[1]}/${repoMatch[2]}`;
              }
            }
            if (!await isValidSkill(realPath)) continue;
            const metadata = await parseSkillMetadata(realPath);
            skills.push({ name: entry.name, title: metadata.title, description: metadata.description, enabled: true, source, repository, path: realPath, scope: 'local', projectPath: projectDir });
          } catch (err) {
            console.error(`Error reading project skill ${entry.name}:`, err.message);
          }
        }
      }
    } catch (err) {
      console.error('[Skills] Error scanning project-scoped skills:', err.message);
    }

    // Compute sync status for each skill
    // For builtin skills, batch-check git status once
    const builtinChangedSkills = new Set();
    const systemRepoTag = `${SYSTEM_REPO_OWNER}/${SYSTEM_REPO_NAME}`;
    const hasBuiltinSkills = skills.some(s => s.repository === systemRepoTag);
    if (hasBuiltinSkills) {
      const publicPaths = getPublicPaths();
      const systemRepoPath = path.join(publicPaths.skillsRepoDir, SYSTEM_REPO_OWNER, SYSTEM_REPO_NAME);
      try {
        const statusOutput = await runGitOutput(['status', '--porcelain'], systemRepoPath);
        for (const line of statusOutput.split('\n')) {
          if (!line) continue;
          const filePath = line.slice(3).trim();
          const topDir = filePath.split('/')[0];
          if (topDir) builtinChangedSkills.add(topDir);
        }
      } catch {
        // System repo may not exist locally or git may fail — skip sync detection
      }
    }

    for (const skill of skills) {
      if (skill.scope === 'local') {
        skill.syncable = false;
        skill.syncType = null;
        continue;
      }
      if (skill.source === 'imported' || skill.source === 'unknown') {
        skill.syncable = true;
        skill.syncType = 'local';
      } else if (skill.repository === systemRepoTag) {
        skill.syncable = builtinChangedSkills.has(skill.name);
        skill.syncType = 'builtin';
      } else if (skill.source === 'repo') {
        skill.syncable = true;
        skill.syncType = 'external';
      } else {
        skill.syncable = false;
        skill.syncType = null;
      }
    }

    res.json({ skills, count: skills.length });
  } catch (error) {
    console.error('Error listing skills:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * POST /api/skills/enable/:name
 * Enable a skill by creating symlink
 */
router.post('/enable/:name', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { name } = req.params;
    const { skillPath } = req.body;

    if (!SKILL_NAME_REGEX.test(name)) {
      return res.status(400).json({ error: 'Invalid skill name' });
    }

    if (!skillPath) {
      return res.status(400).json({ error: 'Skill path is required' });
    }

    const userPaths = getUserPaths(userUuid);
    const linkPath = path.join(userPaths.skillsDir, name);

    // Check if already exists
    try {
      await fs.access(linkPath);
      return res.status(400).json({ error: 'Skill is already enabled' });
    } catch {
      // Good, doesn't exist
    }

    // Create symlink
    await fs.symlink(skillPath, linkPath);

    res.json({ success: true, message: 'Skill enabled' });
  } catch (error) {
    console.error('Error enabling skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * Recursively add directory contents to a zip archive.
 * Uses fs.stat (follows symlinks) so symlinked files are included transparently.
 */
async function addDirToZip(zip, dirPath, zipPrefix) {
  let names;
  try {
    names = await fs.readdir(dirPath);
  } catch {
    return;
  }

  for (const name of names) {
    if (name.startsWith('.')) continue;

    const fullPath = path.join(dirPath, name);
    const zipPath = zipPrefix + name;

    let stat;
    try {
      stat = await fs.stat(fullPath); // follows symlinks
    } catch {
      continue;
    }

    if (stat.isDirectory()) {
      await addDirToZip(zip, fullPath, zipPath + '/');
    } else if (stat.isFile()) {
      const data = await fs.readFile(fullPath);
      zip.addFile(zipPath, data);
    }
  }
}

/**
 * GET /api/skills/:name/download
 * Download a skill as a zip file (symlinks are resolved transparently)
 */
router.get('/:name/download', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { name } = req.params;

    if (!SKILL_NAME_REGEX.test(name)) {
      return res.status(400).json({ error: 'Invalid skill name' });
    }

    const userPaths = getUserPaths(userUuid);
    const skillPath = path.join(userPaths.skillsDir, name);

    // Resolve symlink to real directory
    let realPath = skillPath;
    try {
      const stat = await fs.lstat(skillPath);
      if (stat.isSymbolicLink()) {
        realPath = await fs.realpath(skillPath);
      }
    } catch {
      return res.status(404).json({ error: 'Skill not found' });
    }

    if (!await isValidSkill(realPath)) {
      return res.status(404).json({ error: 'Skill not found or invalid' });
    }

    const zip = new AdmZip();
    await addDirToZip(zip, realPath, `${name}/`);
    const zipBuffer = zip.toBuffer();

    res.setHeader('Content-Type', 'application/zip');
    res.setHeader('Content-Disposition', `attachment; filename="${name}.zip"`);
    res.setHeader('Content-Length', zipBuffer.length);
    res.send(zipBuffer);
  } catch (error) {
    console.error('Error downloading skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * POST /api/skills/:name/sync
 * Sync a skill to the built-in system repository
 */
router.post('/:name/sync', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { name } = req.params;
    if (!SKILL_NAME_REGEX.test(name)) {
      return res.status(400).json({ error: 'Invalid skill name' });
    }

    const userPaths = getUserPaths(userUuid);
    const publicPaths = getPublicPaths();
    const linkPath = path.join(userPaths.skillsDir, name);

    // Resolve skill info
    let realPath;
    let source = 'unknown';
    let repository = null;
    let isSymlink = false;

    try {
      const stat = await fs.lstat(linkPath);
      isSymlink = stat.isSymbolicLink();
      if (isSymlink) {
        realPath = await fs.realpath(linkPath);
        if (realPath.includes('/skills-import/')) {
          source = 'imported';
        } else if (realPath.includes('/skills-repo/')) {
          source = 'repo';
          const repoMatch = realPath.match(/skills-repo\/([^/]+)\/([^/]+)/);
          if (repoMatch) repository = `${repoMatch[1]}/${repoMatch[2]}`;
        }
      } else {
        realPath = linkPath;
      }
    } catch {
      return res.status(404).json({ error: 'Skill not found' });
    }

    if (!await isValidSkill(realPath)) {
      return res.status(400).json({ error: 'Invalid skill: missing SKILL.md' });
    }

    const systemRepoTag = `${SYSTEM_REPO_OWNER}/${SYSTEM_REPO_NAME}`;
    const username = req.user?.email || req.user?.username || req.user?.uuid;
    const now = new Date();
    const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;

    // === Case 2: Built-in system repo skill — push local changes ===
    if (source === 'repo' && repository === systemRepoTag) {
      const systemRepoPath = path.join(publicPaths.skillsRepoDir, SYSTEM_REPO_OWNER, SYSTEM_REPO_NAME);
      try {
        await runGit(['add', name], systemRepoPath);
        await runGit(['commit', '-m', `feat: update skill ${name} by user ${username} at ${timestamp}`], systemRepoPath);
        await runGit(['push'], systemRepoPath);
        console.log(`[SkillSync] Pushed changes for built-in skill "${name}" by ${username}`);
      } catch (e) {
        console.error(`[SkillSync] Failed to push skill "${name}":`, e.message);
        return res.status(500).json({ error: `Git push failed: ${e.message}` });
      }
      return res.json({ success: true, message: '技能修改已同步到云端' });
    }

    // === Case 1 & 3: Local/imported or external repo skill — migrate to system repo ===
    let skillRepoPath;
    try {
      skillRepoPath = await ensureSystemRepo();
    } catch (e) {
      console.error('[SkillSync] Failed to ensure system repo:', e.message);
      return res.status(500).json({ error: `无法访问系统仓库: ${e.message}` });
    }

    const destSkillDir = path.join(skillRepoPath, name);

    // Copy skill files to system repo (overwrite if exists)
    await fs.rm(destSkillDir, { recursive: true, force: true });
    await copyDirRecursive(realPath, destSkillDir);
    console.log(`[SkillSync] Copied skill "${name}" to system repo`);

    // Git: add, commit, push
    try {
      await runGit(['add', name], skillRepoPath);
      await runGit(['commit', '-m', `feat: add skill ${name} by user ${username} at ${timestamp}`], skillRepoPath);
      await runGit(['push'], skillRepoPath);
      console.log(`[SkillSync] Pushed skill "${name}" to system repo`);
    } catch (e) {
      // Rollback: remove the files we just copied
      await fs.rm(destSkillDir, { recursive: true, force: true }).catch(() => {});
      console.error(`[SkillSync] Git push failed for skill "${name}":`, e.message);
      return res.status(500).json({ error: `Git push failed: ${e.message}` });
    }

    // For external repo: restore original files via git checkout
    if (source === 'repo' && repository && repository !== systemRepoTag) {
      const repoMatch = repository.match(/^([^/]+)\/(.+)$/);
      if (repoMatch) {
        const externalRepoPath = path.join(publicPaths.skillsRepoDir, repoMatch[1], repoMatch[2]);
        const skillRelPath = path.relative(externalRepoPath, realPath);
        try {
          await runGit(['checkout', '--', skillRelPath], externalRepoPath);
          console.log(`[SkillSync] Restored external repo skill "${name}" via git checkout`);
        } catch (e) {
          console.warn(`[SkillSync] git checkout on external repo failed (non-fatal):`, e.message);
        }
      }
    }

    // Clean up local import directory (Case 1: imported)
    if (source === 'imported') {
      try {
        await fs.rm(path.join(userPaths.skillsImportDir, name), { recursive: true, force: true });
      } catch {}
    }

    // Remove current symlink/directory
    if (isSymlink) {
      await fs.unlink(linkPath);
    } else {
      await fs.rm(linkPath, { recursive: true, force: true });
    }

    // Re-install from system repo: find skill path and create symlink
    const newSkillPath = await findSkillInSystemRepo(skillRepoPath, name);
    if (newSkillPath) {
      await fs.symlink(newSkillPath, linkPath);
      console.log(`[SkillSync] Re-installed skill "${name}" from system repo`);
    } else {
      console.warn(`[SkillSync] Could not find skill "${name}" in system repo after push — skipping re-install`);
    }

    return res.json({ success: true, message: '技能已成功同步到共享仓库' });
  } catch (error) {
    console.error('Error syncing skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * DELETE /api/skills/disable/:name
 * Disable a skill by removing symlink
 */
router.delete('/disable/:name', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { name } = req.params;

    if (!SKILL_NAME_REGEX.test(name)) {
      return res.status(400).json({ error: 'Invalid skill name' });
    }

    const userPaths = getUserPaths(userUuid);
    const linkPath = path.join(userPaths.skillsDir, name);

    // Verify it's a symlink before removing
    try {
      const stat = await fs.lstat(linkPath);
      if (!stat.isSymbolicLink()) {
        return res.status(400).json({ error: 'Cannot disable non-symlink skill' });
      }
    } catch (err) {
      return res.status(404).json({ error: 'Skill not found' });
    }

    await fs.unlink(linkPath);

    res.json({ success: true, message: 'Skill disabled' });
  } catch (error) {
    console.error('Error disabling skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * DELETE /api/skills/:name
 * Delete a skill completely
 */
router.delete('/:name', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { name } = req.params;
    const { projectPath } = req.query;

    if (!SKILL_NAME_REGEX.test(name)) {
      return res.status(400).json({ error: 'Invalid skill name' });
    }

    const userPaths = getUserPaths(userUuid);
    const linkPath = projectPath
      ? path.join(projectPath, '.claude', 'skills', name)
      : path.join(userPaths.skillsDir, name);

    // Check the symlink target to determine source
    let realPath = null;
    let isSymlink = false;
    let isImported = false;

    try {
      const stat = await fs.lstat(linkPath);
      isSymlink = stat.isSymbolicLink();
      if (isSymlink) {
        realPath = await fs.realpath(linkPath);
        isImported = realPath.includes('/skills-import/');
      }
    } catch (err) {
      return res.status(404).json({ error: 'Skill not found' });
    }

    // Remove symlink or directory
    if (isSymlink) {
      await fs.unlink(linkPath);
    } else {
      // Real directory (local skill) — remove recursively
      await fs.rm(linkPath, { recursive: true, force: true });
    }

    // If imported, also delete the actual files
    if (isImported && realPath) {
      try {
        await fs.rm(realPath, { recursive: true, force: true });
      } catch (err) {
        console.error('Error removing imported skill files:', err);
      }
    }

    res.json({ success: true, message: 'Skill deleted' });
  } catch (error) {
    console.error('Error deleting skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * POST /api/skills/import
 * Import a skill from zip file
 */
router.post('/import', upload.single('skillZip'), async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    if (!req.file) {
      return res.status(400).json({ error: 'ZIP file is required' });
    }

    const userPaths = getUserPaths(userUuid);

    // Extract zip
    const zip = new AdmZip(req.file.buffer);
    const zipEntries = zip.getEntries();

    if (zipEntries.length === 0) {
      return res.status(400).json({ error: 'ZIP file is empty' });
    }

    // Determine skill name from zip structure
    // Look for the root directory or first directory containing SKILL.md
    let skillName = null;
    let rootDir = '';

    for (const entry of zipEntries) {
      if (entry.entryName.endsWith('SKILL.md')) {
        const parts = entry.entryName.split('/');
        if (parts.length >= 2) {
          skillName = parts[0];
          rootDir = parts[0] + '/';
        } else {
          // SKILL.md is at root, use original zip filename
          skillName = path.basename(req.file.originalname, '.zip');
        }
        break;
      }
    }

    if (!skillName) {
      // Fallback to first directory or zip filename
      const firstEntry = zipEntries.find(e => e.isDirectory);
      if (firstEntry) {
        skillName = firstEntry.entryName.replace(/\/$/, '').split('/')[0];
        rootDir = skillName + '/';
      } else {
        skillName = path.basename(req.file.originalname, '.zip');
      }
    }

    // Validate skill name
    if (!SKILL_NAME_REGEX.test(skillName)) {
      skillName = skillName.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 100);
    }

    const importDir = path.join(userPaths.skillsImportDir, skillName);

    // Ensure import directory exists and is empty
    await fs.rm(importDir, { recursive: true, force: true });
    await fs.mkdir(importDir, { recursive: true });

    // Extract files
    for (const entry of zipEntries) {
      if (entry.isDirectory) continue;

      let targetPath = entry.entryName;
      if (rootDir && targetPath.startsWith(rootDir)) {
        targetPath = targetPath.slice(rootDir.length);
      }

      if (!targetPath) continue;

      const fullPath = path.join(importDir, targetPath);
      const dir = path.dirname(fullPath);

      await fs.mkdir(dir, { recursive: true });
      await fs.writeFile(fullPath, entry.getData());
    }

    // Create symlink in user's skills directory
    const linkPath = path.join(userPaths.skillsDir, skillName);

    // Remove existing symlink if any
    try {
      await fs.unlink(linkPath);
    } catch {
      // Ignore
    }

    await fs.symlink(importDir, linkPath);

    const metadata = await parseSkillMetadata(importDir);

    res.json({
      success: true,
      skill: {
        name: skillName,
        title: metadata.title,
        description: metadata.description,
        source: 'imported'
      }
    });
  } catch (error) {
    console.error('Error importing skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * GET /api/skills/available
 * List all available skills from repositories
 */
router.get('/available', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const userPaths = getUserPaths(userUuid);
    const publicPaths = getPublicPaths();

    const skills = [];

    // Get user's installed skills for comparison
    const installedSkills = new Set();
    try {
      const installed = await fs.readdir(userPaths.skillsDir);
      installed.forEach(s => installedSkills.add(s));
    } catch {
      // Ignore
    }

    // Scan user's repo symlinks
    try {
      await fs.mkdir(userPaths.skillsRepoDir, { recursive: true });
      const owners = await fs.readdir(userPaths.skillsRepoDir);

      for (const owner of owners) {
        if (owner.startsWith('.')) continue;

        const ownerPath = path.join(userPaths.skillsRepoDir, owner);
        const stat = await fs.stat(ownerPath);
        if (!stat.isDirectory()) continue;

        const repos = await fs.readdir(ownerPath);

        for (const repo of repos) {
          if (repo.startsWith('.')) continue;

          const repoPath = path.join(ownerPath, repo);
          let realRepoPath = repoPath;

          try {
            const repoStat = await fs.lstat(repoPath);
            if (repoStat.isSymbolicLink()) {
              realRepoPath = await fs.realpath(repoPath);
            }
          } catch {
            continue;
          }

          // Scan for skills in the repo (supports nested dirs like skills/XXX)
          const repoSkills = await scanDirForSkills(realRepoPath, `${owner}/${repo}`, installedSkills);
          skills.push(...repoSkills);
        }
      }
    } catch (err) {
      console.error('Error scanning repos:', err);
    }

    res.json({ skills });
  } catch (error) {
    console.error('Error listing available skills:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * POST /api/skills/install/:name
 * Install a skill from repository
 */
router.post('/install/:name', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { name } = req.params;
    const { skillPath, projectPath } = req.body;

    if (!SKILL_NAME_REGEX.test(name)) {
      return res.status(400).json({ error: 'Invalid skill name' });
    }

    if (!skillPath) {
      return res.status(400).json({ error: 'Skill path is required' });
    }

    const userPaths = getUserPaths(userUuid);

    // Verify skill exists
    if (!await isValidSkill(skillPath)) {
      return res.status(404).json({ error: 'Skill not found or invalid' });
    }

    // Determine target directory: project-scoped or user global
    const targetDir = projectPath
      ? path.join(projectPath, '.claude', 'skills')
      : userPaths.skillsDir;
    await fs.mkdir(targetDir, { recursive: true });
    const userSkillLink = path.join(targetDir, name);

    try {
      await fs.unlink(userSkillLink);
    } catch {
      // Ignore
    }

    await fs.symlink(skillPath, userSkillLink);

    const metadata = await parseSkillMetadata(skillPath);

    res.json({
      success: true,
      skill: {
        name,
        title: metadata.title,
        description: metadata.description,
        source: 'repo'
      }
    });
  } catch (error) {
    console.error('Error installing skill:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * GET /api/skills/repos
 * List user's added skill repositories
 */
router.get('/repos', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const userPaths = getUserPaths(userUuid);
    const repos = [];

    try {
      await fs.mkdir(userPaths.skillsRepoDir, { recursive: true });
      const owners = await fs.readdir(userPaths.skillsRepoDir);

      for (const owner of owners) {
        if (owner.startsWith('.')) continue;

        const ownerPath = path.join(userPaths.skillsRepoDir, owner);
        const stat = await fs.stat(ownerPath);
        if (!stat.isDirectory()) continue;

        const repoNames = await fs.readdir(ownerPath);

        for (const repo of repoNames) {
          if (repo.startsWith('.')) continue;

          const repoPath = path.join(ownerPath, repo);
          let realPath = repoPath;

          try {
            const repoStat = await fs.lstat(repoPath);
            if (repoStat.isSymbolicLink()) {
              realPath = await fs.realpath(repoPath);
            }
          } catch {
            continue;
          }

          // Count skills in repo (supports nested dirs like skills/XXX)
          const repoSkills = await scanDirForSkills(realPath, `${owner}/${repo}`, null);
          const skillCount = repoSkills.length;
          const isSystem = owner === SYSTEM_REPO_OWNER && repo === SYSTEM_REPO_NAME;

          repos.push({
            owner,
            repo,
            url: isSystem ? null : `https://github.com/${owner}/${repo}`,
            skillCount,
            path: realPath,
            isSystem
          });
        }
      }
    } catch (err) {
      console.error('Error reading repos:', err);
    }

    res.json({ repos });
  } catch (error) {
    console.error('Error listing repos:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * POST /api/skills/repos
 * Add (clone) a skill repository
 */
router.post('/repos', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    let { url, branch = 'main' } = req.body;

    // Handle short format: owner/repo -> https://github.com/owner/repo
    if (url && !url.includes('://') && /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(url.trim())) {
      url = `https://github.com/${url.trim()}`;
    }

    // Validate URL
    const validation = validateGitUrl(url);
    if (!validation.valid) {
      return res.status(400).json({ error: validation.error });
    }

    // Parse owner/repo from URL
    const parsed = parseGitUrl(url);
    if (!parsed) {
      return res.status(400).json({ error: 'Could not parse repository URL' });
    }

    const { owner, repo } = parsed;
    const userPaths = getUserPaths(userUuid);
    const publicPaths = getPublicPaths();

    // Public repo path
    const publicRepoPath = path.join(publicPaths.skillsRepoDir, owner, repo);

    // Check if already cloned publicly
    let needsClone = true;
    try {
      await fs.access(publicRepoPath);
      needsClone = false;
      // Try to update
      try {
        await updateRepository(publicRepoPath);
      } catch (err) {
        console.log('Failed to update repo, using existing:', err.message);
      }
    } catch {
      // Need to clone
    }

    if (needsClone) {
      // Clone to public directory
      await fs.mkdir(path.dirname(publicRepoPath), { recursive: true });
      await cloneRepository(url, publicRepoPath);
    }

    // Create user symlink
    const userRepoPath = path.join(userPaths.skillsRepoDir, owner, repo);
    await fs.mkdir(path.dirname(userRepoPath), { recursive: true });

    try {
      await fs.unlink(userRepoPath);
    } catch {
      // Ignore
    }

    await fs.symlink(publicRepoPath, userRepoPath);

    // Count skills (supports nested dirs like skills/XXX)
    let skillCount = 0;
    try {
      const repoSkills = await scanDirForSkills(publicRepoPath, `${owner}/${repo}`, null);
      skillCount = repoSkills.length;
    } catch {
      // Ignore
    }

    res.json({
      success: true,
      repo: {
        owner,
        repo,
        url,
        skillCount,
        path: publicRepoPath
      }
    });
  } catch (error) {
    console.error('Error adding repo:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * POST /api/skills/repos/refresh
 * Pull latest updates for all user's skill repositories
 */
router.post('/repos/refresh', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const userPaths = getUserPaths(userUuid);
    const results = [];

    try {
      await fs.mkdir(userPaths.skillsRepoDir, { recursive: true });
      const owners = await fs.readdir(userPaths.skillsRepoDir);

      for (const owner of owners) {
        if (owner.startsWith('.')) continue;

        const ownerPath = path.join(userPaths.skillsRepoDir, owner);
        const stat = await fs.stat(ownerPath);
        if (!stat.isDirectory()) continue;

        const repoNames = await fs.readdir(ownerPath);

        for (const repo of repoNames) {
          if (repo.startsWith('.')) continue;

          const repoPath = path.join(ownerPath, repo);
          let realPath = repoPath;

          try {
            const repoStat = await fs.lstat(repoPath);
            if (repoStat.isSymbolicLink()) {
              realPath = await fs.realpath(repoPath);
            }
          } catch {
            continue;
          }

          try {
            await updateRepository(realPath);
            results.push({ owner, repo, status: 'updated' });
          } catch (err) {
            console.log(`Failed to update ${owner}/${repo}:`, err.message);
            results.push({ owner, repo, status: 'failed', error: err.message });
          }
        }
      }
    } catch (err) {
      console.error('Error refreshing repos:', err);
    }

    res.json({ success: true, results });
  } catch (error) {
    console.error('Error refreshing repos:', error);
    res.status(500).json({ error: error.message });
  }
});

/**
 * DELETE /api/skills/repos/:owner/:repo
 * Remove a skill repository (user's symlink only)
 */
router.delete('/repos/:owner/:repo', async (req, res) => {
  try {
    const userUuid = req.user?.uuid;
    if (!userUuid) {
      return res.status(401).json({ error: 'User authentication required' });
    }

    const { owner, repo } = req.params;

    if (!owner || !repo) {
      return res.status(400).json({ error: 'Owner and repo are required' });
    }

    // System repo cannot be removed
    if (owner === SYSTEM_REPO_OWNER && repo === SYSTEM_REPO_NAME) {
      return res.status(403).json({ error: '内置仓库不能删除' });
    }

    const userPaths = getUserPaths(userUuid);
    const userRepoPath = path.join(userPaths.skillsRepoDir, owner, repo);

    try {
      await fs.unlink(userRepoPath);
    } catch (err) {
      if (err.code === 'ENOENT') {
        return res.status(404).json({ error: 'Repository not found' });
      }
      throw err;
    }

    // Try to remove empty parent directory
    try {
      await fs.rmdir(path.dirname(userRepoPath));
    } catch {
      // Ignore - directory not empty
    }

    res.json({ success: true, message: 'Repository removed' });
  } catch (error) {
    console.error('Error removing repo:', error);
    res.status(500).json({ error: error.message });
  }
});

export default router;
