import { promises as fs } from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import { getPublicPaths } from './user-directories.js';

export const SYSTEM_AGENT_REPO_URL = 'git@git.amberweather.com:mcp-server/hupoer-agents.git';
export const SYSTEM_AGENT_REPO_OWNER = 'mcp-server';
export const SYSTEM_AGENT_REPO_NAME = 'hupoer-agents';


function runGit(args, cwd = null) {
  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, stderr });
      else reject(new Error(stderr || `git ${args[0]} failed with code ${code}`));
    });
    proc.on('error', err => reject(err));
  });
}

function getRepoPath() {
  const publicPaths = getPublicPaths();
  return path.join(publicPaths.agentRepoDir, SYSTEM_AGENT_REPO_OWNER, SYSTEM_AGENT_REPO_NAME);
}

/**
 * Ensure the agent repo is cloned locally. Does NOT pull; call pullAgentRepo() explicitly.
 * Returns the path to the local clone.
 */
export async function ensureAgentRepo() {
  const repoPath = getRepoPath();

  try {
    await fs.access(repoPath);
  } catch {
    // Not yet cloned
    await fs.mkdir(path.dirname(repoPath), { recursive: true });
    try {
      await runGit(['clone', '--depth', '1', SYSTEM_AGENT_REPO_URL, repoPath]);
      console.log('[AgentRepo] Cloned agent repo to', repoPath);
    } catch (err) {
      console.error('[AgentRepo] Failed to clone:', err.message);
      throw err;
    }
  }

  return repoPath;
}

/**
 * Pull latest from remote. Called explicitly on user-triggered refresh.
 */
export async function pullAgentRepo() {
  const repoPath = getRepoPath();
  try {
    await runGit(['pull', '--ff-only'], repoPath);
  } catch (err) {
    console.log('[AgentRepo] Failed to pull:', err.message);
    throw err;
  }
}

/**
 * Parse agent.yaml from an agent directory.
 * Returns the parsed metadata object or null if invalid.
 */
async function parseAgentYaml(agentDir) {
  try {
    const yamlPath = path.join(agentDir, 'agent.yaml');
    const content = await fs.readFile(yamlPath, 'utf-8');

    // Simple YAML parser for the expected fields
    const result = {
      name: path.basename(agentDir),
      display_name: path.basename(agentDir),
      description: '',
      version: '1.0.0',
      author: '',
      skills: [],
      mcps: [],
      files: [],
      git_repos: []
    };

    // Parse scalar fields
    const scalarPattern = (key) => new RegExp(`^${key}:\\s*["']?(.+?)["']?\\s*$`, 'm');
    const nameMatch = content.match(scalarPattern('name'));
    const displayMatch = content.match(scalarPattern('display_name'));
    const versionMatch = content.match(scalarPattern('version'));
    const authorMatch = content.match(scalarPattern('author'));

    if (nameMatch) result.name = nameMatch[1].trim();
    if (displayMatch) result.display_name = displayMatch[1].trim();
    if (versionMatch) result.version = versionMatch[1].trim();
    if (authorMatch) result.author = authorMatch[1].trim();

    // Parse block scalar description (|)
    const descBlockMatch = content.match(/^description:\s*\|$([\s\S]*?)(?=^\S|\Z)/m);
    if (descBlockMatch) {
      result.description = descBlockMatch[1]
        .split('\n')
        .map(l => l.replace(/^  /, ''))
        .join('\n')
        .trim();
    } else {
      const descInlineMatch = content.match(/^description:\s*(.+)$/m);
      if (descInlineMatch) result.description = descInlineMatch[1].trim();
    }

    // Parse skills list
    const skillsSection = content.match(/^skills:\s*\n((?:[ \t]+.+\n?)*)/m);
    if (skillsSection) {
      const lines = skillsSection[1].split('\n');
      let current = null;
      for (const line of lines) {
        const nameLine = line.match(/^\s+-\s+name:\s*["']?(.+?)["']?\s*$/);
        const repoLine = line.match(/^\s+repo:\s*["']?(.+?)["']?\s*$/);
        if (nameLine) { current = { name: nameLine[1].trim() }; result.skills.push(current); }
        else if (repoLine && current) { current.repo = repoLine[1].trim(); }
      }
    }

    // Parse mcps list
    const mcpsSection = content.match(/^mcps:\s*\n((?:[ \t]+.+\n?)*)/m);
    if (mcpsSection) {
      const lines = mcpsSection[1].split('\n');
      let current = null;
      for (const line of lines) {
        const nameLine = line.match(/^\s+-\s+name:\s*["']?(.+?)["']?\s*$/);
        const repoLine = line.match(/^\s+repo:\s*["']?(.+?)["']?\s*$/);
        if (nameLine) { current = { name: nameLine[1].trim() }; result.mcps.push(current); }
        else if (repoLine && current) { current.repo = repoLine[1].trim(); }
      }
    }

    // Parse files list
    const filesSection = content.match(/^files:\s*\n((?:[ \t]+.+\n?)*)/m);
    if (filesSection) {
      const lines = filesSection[1].split('\n');
      for (const line of lines) {
        const fileLine = line.match(/^\s+-\s+["']?(.+?)["']?\s*$/);
        if (fileLine) result.files.push(fileLine[1].trim());
      }
    }

    // Parse git_repos list
    const gitReposSection = content.match(/^git_repos:\s*\n((?:[ \t]+.+\n?)*)/m);
    if (gitReposSection) {
      const lines = gitReposSection[1].split('\n');
      let current = null;
      for (const line of lines) {
        const nameLine = line.match(/^\s+-\s+name:\s*["']?(.+?)["']?\s*$/);
        const repoLine = line.match(/^\s+repo:\s*["']?(.+?)["']?\s*$/);
        const branchLine = line.match(/^\s+branch:\s*["']?(.+?)["']?\s*$/);
        if (nameLine) { current = { name: nameLine[1].trim() }; result.git_repos.push(current); }
        else if (repoLine && current) { current.repo = repoLine[1].trim(); }
        else if (branchLine && current) { current.branch = branchLine[1].trim(); }
      }
    }

    return result;
  } catch {
    return null;
  }
}

/**
 * Scan the agent repo for available agents.
 * @param {boolean} pull - When true, git pull before scanning (e.g. user-triggered refresh).
 *                         When false (default), read directly from local disk — fast path.
 * Returns array of agent metadata objects.
 */
export async function scanAgents(pull = false) {
  let repoPath;
  try {
    repoPath = await ensureAgentRepo();
  } catch (err) {
    console.error('[AgentRepo] Could not access agent repo:', err.message);
    return [];
  }

  if (pull) {
    try {
      await pullAgentRepo();
    } catch (err) {
      console.warn('[AgentRepo] Pull failed, scanning existing local data:', err.message);
    }
  }

  const agents = [];

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

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

    const agentDir = path.join(repoPath, entry.name);
    const metadata = await parseAgentYaml(agentDir);
    if (!metadata) continue;

    // Check for CLAUDE.md
    let hasClaudeMd = false;
    try {
      await fs.access(path.join(agentDir, 'CLAUDE.md'));
      hasClaudeMd = true;
    } catch {}

    agents.push({
      ...metadata,
      dirName: entry.name,
      path: agentDir,
      hasClaudeMd
    });
  }

  return agents;
}

/**
 * Increment the patch version of a semver string.
 * e.g. "1.0.0" → "1.0.1", "2.3" → "2.3.1"
 */
export function incrementPatchVersion(version) {
  if (!version) return '1.0.0';
  const parts = version.split('.').map(n => parseInt(n, 10) || 0);
  while (parts.length < 3) parts.push(0);
  parts[2] += 1;
  return parts.join('.');
}

/**
 * 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);
    }
  }
}

/**
 * Publish an agent to the repo after approval.
 * Steps: pull → copy files → update agent.yaml version → git add/commit/push
 *
 * @param {string} agentName - directory name in the repo
 * @param {string} extractedDir - local directory with the approved agent files
 * @param {string} newVersion - new version string
 * @param {string} submitterName - for commit message attribution
 */
export async function publishAgentToRepo(agentName, extractedDir, newVersion, submitterName = 'system') {
  const repoPath = getRepoPath();

  // 1. Pull latest
  try {
    await runGit(['pull', '--ff-only'], repoPath);
  } catch (err) {
    console.error('[AgentRepo] Pull before publish failed:', err.message);
    throw err;
  }

  // 2. Replace agent directory entirely to ensure removed files are cleaned up.
  //    Delete the old directory first, then copy fresh from the submitted archive.
  //    git add will pick up both the new/modified files and the deletions.
  const agentRepoDir = path.join(repoPath, agentName);
  try {
    await fs.rm(agentRepoDir, { recursive: true, force: true });
  } catch (err) {
    console.error('[AgentRepo] Failed to remove old agent dir:', err.message);
  }
  await copyDirRecursive(extractedDir, agentRepoDir);

  // 3. Update agent.yaml with new version and updated_at
  const yamlPath = path.join(agentRepoDir, 'agent.yaml');
  try {
    let yamlContent = await fs.readFile(yamlPath, 'utf-8');
    const now = new Date().toISOString();
    yamlContent = yamlContent.replace(/^version:\s*.+$/m, `version: "${newVersion}"`);
    yamlContent = yamlContent.replace(/^updated_at:\s*.+$/m, `updated_at: "${now}"`);
    if (!yamlContent.match(/^updated_at:/m)) {
      yamlContent += `\nupdated_at: "${now}"\n`;
    }
    await fs.writeFile(yamlPath, yamlContent, 'utf-8');
  } catch (err) {
    console.error('[AgentRepo] Failed to update agent.yaml version:', err.message);
  }

  // 4. Git add, commit, push
  await runGit(['add', agentName], repoPath);
  const commitMsg = `feat: publish ${agentName} v${newVersion} (by ${submitterName})`;
  await runGit(['commit', '-m', commitMsg], repoPath);
  await runGit(['push'], repoPath);

  console.log(`[AgentRepo] Published ${agentName} v${newVersion}`);
}
