'use strict';

const path = require('path');
const { spawnSync } = require('child_process');

function getContainerContext() {
  const name = process.env.EASYAGENT_CONTAINER_NAME;
  if (!name) return null;
  return {
    containerName: name,
    dockerBin: process.env.EASYAGENT_DOCKER_BIN || 'docker',
    mountPath: (process.env.EASYAGENT_CONTAINER_MOUNT_PATH || '/workspace').replace(/\/+$/, '') || '/workspace',
    containerVenv: process.env.EASYAGENT_CONTAINER_VENV || '/opt/agent-venv',
    containerPython: process.env.EASYAGENT_CONTAINER_PYTHON || '/opt/agent-venv/bin/python3',
  };
}

function normalizePosix(p) {
  return p.split(path.sep).join('/');
}

function resolveContainerPath(workspace, inputPath, ctx) {
  const hostRoot = path.resolve(workspace);
  const containerRoot = ctx.mountPath;
  let hostPath = hostRoot;
  let containerPath = containerRoot;

  if (inputPath) {
    if (path.isAbsolute(inputPath)) {
      if (inputPath.startsWith(containerRoot)) {
        containerPath = inputPath;
        const rel = path.posix.relative(containerRoot, inputPath);
        hostPath = path.join(hostRoot, ...rel.split('/'));
      } else if (inputPath.startsWith(hostRoot)) {
        hostPath = inputPath;
        const rel = path.relative(hostRoot, inputPath);
        containerPath = path.posix.join(containerRoot, normalizePosix(rel));
      } else {
        return { error: '路径必须位于工作区内' };
      }
    } else {
      hostPath = path.resolve(hostRoot, inputPath);
      if (!hostPath.startsWith(hostRoot)) {
        return { error: '路径必须位于工作区内' };
      }
      const rel = path.relative(hostRoot, hostPath);
      containerPath = path.posix.join(containerRoot, normalizePosix(rel));
    }
  }

  const relContainer = path.posix.relative(containerRoot, containerPath);
  return {
    hostRoot,
    hostPath,
    containerPath,
    relativePath: relContainer || '.',
  };
}

const CONTAINER_SCRIPT = `
import json
import sys
import pathlib
import shutil
import os
import re
import fnmatch

def _resolve(root: pathlib.Path, rel: str) -> pathlib.Path:
    base = root.resolve()
    target = (base / rel).resolve()
    if not str(target).startswith(str(base)):
        raise ValueError("路径越界: %s" % rel)
    return target

def _read_text(target: pathlib.Path):
    with target.open('r', encoding='utf-8', errors='ignore') as fh:
        data = fh.read()
    lines = data.splitlines(keepends=True)
    return data, lines

def _ensure_file(target: pathlib.Path):
    if not target.exists():
        return {"success": False, "error": "文件不存在"}
    if not target.is_file():
        return {"success": False, "error": "不是文件"}
    return None

def _read_text_segment(root, payload):
    rel = payload.get("path")
    start = payload.get("start_line")
    end = payload.get("end_line")
    target = _resolve(root, rel)
    err = _ensure_file(target)
    if err:
        return err
    data, lines = _read_text(target)
    total = len(lines)
    line_start = start if start and start > 0 else 1
    line_end = end if end and end >= line_start else total
    if line_start > total:
        return {"success": False, "error": "起始行超出文件长度"}
    line_end = min(line_end, total)
    snippet = "".join(lines[line_start - 1 : line_end])
    return {
        "success": True,
        "path": rel,
        "content": snippet,
        "line_start": line_start,
        "line_end": line_end,
        "total_lines": total
    }

def _read_file(root, payload):
    rel = payload.get("path")
    target = _resolve(root, rel)
    err = _ensure_file(target)
    if err:
        return err
    with target.open('r', encoding='utf-8', errors='ignore') as fh:
        content = fh.read()
    return {"success": True, "path": rel, "content": content, "size": len(content)}

def _write_file(root, payload):
    rel = payload.get("path")
    content = payload.get("content") or ""
    mode = payload.get("mode") or "w"
    target = _resolve(root, rel)
    target.parent.mkdir(parents=True, exist_ok=True)
    with target.open(mode, encoding='utf-8') as fh:
        fh.write(content)
    return {"success": True, "path": rel, "size": len(content), "mode": mode}

def _search_text(root, payload):
    rel = payload.get("path")
    target = _resolve(root, rel)
    err = _ensure_file(target)
    if err:
        return err
    data, lines = _read_text(target)
    total = len(lines)
    query = payload.get("query") or ""
    if not query:
        return {"success": False, "error": "缺少搜索关键词"}
    max_matches = payload.get("max_matches") or 20
    before = payload.get("context_before") or 0
    after = payload.get("context_after") or 0
    case_sensitive = bool(payload.get("case_sensitive"))
    query_cmp = query if case_sensitive else query.lower()

    def contains(text):
        text_cmp = text if case_sensitive else text.lower()
        return query_cmp in text_cmp

    matches = []
    for idx, line in enumerate(lines, start=1):
        if contains(line):
            win_start = max(1, idx - before)
            win_end = min(total, idx + after)
            if matches and win_start <= matches[-1]["line_end"]:
                matches[-1]["line_end"] = max(matches[-1]["line_end"], win_end)
                matches[-1]["hits"].append(idx)
            else:
                if len(matches) >= max_matches:
                    break
                matches.append({
                    "line_start": win_start,
                    "line_end": win_end,
                    "hits": [idx]
                })
    for window in matches:
        snippet_lines = lines[window["line_start"] - 1 : window["line_end"]]
        window["snippet"] = "".join(snippet_lines)

    return {"success": True, "path": rel, "matches": matches}

def _extract_segments(root, payload):
    rel = payload.get("path")
    target = _resolve(root, rel)
    err = _ensure_file(target)
    if err:
        return err
    segments = payload.get("segments") or []
    if not segments:
        return {"success": False, "error": "缺少要提取的行区间"}
    _, lines = _read_text(target)
    total = len(lines)
    extracted = []
    for seg in segments:
        start = max(1, int(seg.get("start_line") or 1))
        end = max(start, int(seg.get("end_line") or start))
        end = min(end, total)
        extracted.append({
            "label": seg.get("label") or "segment",
            "line_start": start,
            "line_end": end,
            "content": "".join(lines[start - 1:end])
        })
    return {"success": True, "path": rel, "segments": extracted}

def _matches_glob(rel_path, patterns):
    if not patterns:
        return True
    for pat in patterns:
        if fnmatch.fnmatch(rel_path, pat):
            return True
    return False

def _search_workspace(root, payload):
    mode = payload.get("mode")
    query = payload.get("query") or ""
    use_regex = bool(payload.get("use_regex"))
    case_sensitive = bool(payload.get("case_sensitive"))
    max_results = payload.get("max_results") or 20
    max_matches_per_file = payload.get("max_matches_per_file") or 3
    include_glob = payload.get("include_glob") or ["**/*"]
    exclude_glob = payload.get("exclude_glob") or []
    max_file_size = payload.get("max_file_size")

    matcher = re.compile(query, 0 if case_sensitive else re.IGNORECASE) if use_regex else None
    results = []
    matches = []
    root = root.resolve()

    for dirpath, _, filenames in os.walk(root):
        for filename in filenames:
            full = pathlib.Path(dirpath) / filename
            try:
                rel = full.relative_to(root).as_posix()
            except Exception:
                continue
            if not _matches_glob(rel, include_glob):
                continue
            if exclude_glob and _matches_glob(rel, exclude_glob):
                continue
            if mode == "file":
                hay = filename if case_sensitive else filename.lower()
                needle = query if case_sensitive else query.lower()
                ok = matcher.search(filename) if matcher else (needle in hay)
                if ok:
                    matches.append(str(full))
                    if len(matches) >= max_results:
                        return {"success": True, "mode": "file", "root": str(root), "query": query, "matches": matches}
                continue

            if mode != "content":
                continue

            if max_file_size:
                try:
                    if full.stat().st_size > max_file_size:
                        continue
                except Exception:
                    continue
            try:
                with full.open('r', encoding='utf-8', errors='ignore') as fh:
                    lines = fh.readlines()
            except Exception:
                continue
            file_matches = []
            for idx, line in enumerate(lines, start=1):
                hay = line if case_sensitive else line.lower()
                needle = query if case_sensitive else query.lower()
                ok = matcher.search(line) if matcher else (needle in hay)
                if ok:
                    file_matches.append({"line": idx, "snippet": line.strip()})
                    if len(file_matches) >= max_matches_per_file:
                        break
            if file_matches:
                results.append({"file": str(full), "matches": file_matches})
                if len(results) >= max_results:
                    return {"success": True, "mode": "content", "root": str(root), "query": query, "results": results}

    if mode == "file":
        return {"success": True, "mode": "file", "root": str(root), "query": query, "matches": matches}
    if mode == "content":
        return {"success": True, "mode": "content", "root": str(root), "query": query, "results": results}
    return {"success": False, "error": "未知 search_workspace mode"}

ACTION_MAP = {
    "read_text_segment": _read_text_segment,
    "read_file": _read_file,
    "write_file": _write_file,
    "search_text": _search_text,
    "extract_segments": _extract_segments,
    "search_workspace": _search_workspace,
}

def main():
    raw = sys.stdin.read()
    payload = json.loads(raw or "{}")
    action = payload.get("action")
    root = pathlib.Path(payload.get("root") or "/workspace")
    handler = ACTION_MAP.get(action)
    if not handler:
        sys.stdout.write(json.dumps({"success": False, "error": "未知操作"}, ensure_ascii=False))
        return
    try:
        result = handler(root, payload.get("payload") or {})
    except Exception as exc:
        result = {"success": False, "error": str(exc)}
    sys.stdout.write(json.dumps(result, ensure_ascii=False))

if __name__ == "__main__":
    main()
`;

function buildEnvArgs(ctx) {
  const venv = ctx.containerVenv || '/opt/agent-venv';
  const basePath = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
  const pathValue = `${venv}/bin:${basePath}`;
  return ['-e', `VIRTUAL_ENV=${venv}`, '-e', `PATH=${pathValue}`];
}

function execContainerAction(ctx, action, payload, timeoutMs, rootOverride) {
  const request = { action, root: rootOverride || ctx.mountPath, payload };
  const args = [ctx.dockerBin, 'exec', '-i'];
  const workdir = rootOverride || ctx.mountPath;
  if (workdir) {
    args.push('-w', workdir);
  }
  args.push(...buildEnvArgs(ctx));
  args.push(ctx.containerName, ctx.containerPython || 'python3', '-c', CONTAINER_SCRIPT);
  const result = spawnSync(args[0], args.slice(1), {
    input: JSON.stringify(request),
    encoding: 'utf8',
    timeout: timeoutMs || 60000,
    maxBuffer: 10 * 1024 * 1024,
  });
  if (result.error) {
    return { success: false, error: result.error.message || String(result.error) };
  }
  if (result.status !== 0) {
    const stderr = (result.stderr || '').trim();
    const stdout = (result.stdout || '').trim();
    return { success: false, error: stderr || stdout || '容器返回错误' };
  }
  const output = (result.stdout || '').trim();
  if (!output) {
    return { success: false, error: '容器未返回结果' };
  }
  try {
    return JSON.parse(output);
  } catch (err) {
    return { success: false, error: `容器响应无法解析: ${output.slice(0, 200)}` };
  }
}

module.exports = {
  getContainerContext,
  resolveContainerPath,
  execContainerAction,
  buildEnvArgs,
};
