#!/usr/bin/env python3
"""Development Agent harness for zhctprompt.

This script checks the minimum structure required before a development-agent
output can be treated as review-ready.
"""

from __future__ import annotations

import argparse
import csv
import subprocess
import sys
from pathlib import Path


REQUIRED_BOOTSTRAP_PATHS = [
    "DEVELOPMENT_AGENT_CONSOLE.md",
    "DEVELOPMENT_AGENT_CONSOLE.html",
    "control/development-agent/README.md",
    "control/development-agent/agent-contract.md",
    "control/development-agent/folder-map.csv",
    "control/development-agent/human-review-guide.md",
    "control/development-agent/task-pack-template.md",
    "control/development-agent/harness/README.md",
    "control/development-agent/harness/checks.csv",
    "control/development-agent/harness/dev_agent_harness.py",
    "modules/product/development/README.md",
    "standards-stack/ai-engineering/README.md",
]

SUMMARY_KEYWORD_GROUPS = [
    ("goal", ["任务目标", "目标"]),
    ("verification", ["验证", "校验"]),
    ("boundary", ["边界", "阻塞", "未验证"]),
    ("next_step", ["下一步", "后续"]),
]

SOURCE_INDEX_FIELD_GROUPS = [
    ("source", ["source", "source_id", "source_name", "来源"]),
    ("path", ["path", "路径"]),
    ("evidence", ["evidence", "evidence_level", "证据"]),
    ("usage", ["usage", "用途"]),
]


class Harness:
    def __init__(self, repo_root: Path) -> None:
        self.repo_root = repo_root
        self.errors: list[str] = []
        self.warnings: list[str] = []

    def path(self, rel: str | Path) -> Path:
        return self.repo_root / rel

    def fail(self, check_id: str, message: str) -> None:
        self.errors.append(f"{check_id}: {message}")

    def warn(self, check_id: str, message: str) -> None:
        self.warnings.append(f"{check_id}: {message}")

    def require_paths(self) -> None:
        for rel in REQUIRED_BOOTSTRAP_PATHS:
            if not self.path(rel).exists():
                self.fail("H001", f"missing required path: {rel}")

    def parse_csv(self, rel: str | Path) -> list[dict[str, str]]:
        path = self.path(rel)
        if not path.exists():
            self.fail("H002", f"missing csv: {rel}")
            return []
        try:
            with path.open(newline="", encoding="utf-8-sig") as fh:
                reader = csv.DictReader(fh)
                rows = list(reader)
        except Exception as exc:  # noqa: BLE001 - harness should report any parse issue
            self.fail("H002", f"cannot parse csv {rel}: {exc}")
            return []
        if not reader.fieldnames:
            self.fail("H002", f"csv has no header: {rel}")
            return []
        return rows

    def validate_bootstrap_csvs(self) -> None:
        for rel in [
            "control/development-agent/folder-map.csv",
            "control/development-agent/harness/checks.csv",
        ]:
            self.parse_csv(rel)

    def validate_task_index(self) -> None:
        script = self.path("control/scripts/task_index.py")
        if not script.exists():
            self.fail("H003", "missing control/scripts/task_index.py")
            return
        result = subprocess.run(
            [sys.executable, str(script), "validate"],
            cwd=self.repo_root,
            text=True,
            capture_output=True,
            check=False,
        )
        if result.returncode != 0:
            self.fail("H003", f"task index validate failed: {result.stdout}{result.stderr}")

    def validate_human_html(self) -> None:
        html = self.path("DEVELOPMENT_AGENT_CONSOLE.html")
        if not html.exists() or html.stat().st_size == 0:
            self.fail("H004", "DEVELOPMENT_AGENT_CONSOLE.html missing or empty")

    def validate_task_dir(self, task_dir: Path) -> None:
        rel = task_dir if task_dir.is_absolute() else self.path(task_dir)
        if not rel.exists():
            self.fail("H005", f"task dir does not exist: {task_dir}")
            return
        summary = rel / "summary.md"
        source_index = rel / "source-index.csv"
        if not summary.exists():
            self.fail("H005", f"missing summary.md in {task_dir}")
        if not source_index.exists():
            self.fail("H005", f"missing source-index.csv in {task_dir}")
        if summary.exists():
            self.validate_summary(summary)
        if source_index.exists():
            self.validate_source_index(source_index)

    def validate_summary(self, summary: Path) -> None:
        text = summary.read_text(encoding="utf-8", errors="replace")
        for group_name, words in SUMMARY_KEYWORD_GROUPS:
            if not any(word in text for word in words):
                self.fail("H006", f"summary.md missing {group_name} section/keyword: {summary}")

    def validate_source_index(self, source_index: Path) -> None:
        rel = source_index.relative_to(self.repo_root) if source_index.is_absolute() else source_index
        rows = self.parse_csv(rel)
        if not rows:
            self.fail("H007", f"source-index.csv has no rows: {source_index}")
            return
        headers = set(rows[0].keys())
        lower_headers = {h.lower() for h in headers if h}
        for group_name, candidates in SOURCE_INDEX_FIELD_GROUPS:
            if not any(c in headers or c.lower() in lower_headers for c in candidates):
                self.fail("H007", f"source-index.csv missing {group_name} field: {source_index}")

    def run_bootstrap(self) -> None:
        self.require_paths()
        self.validate_bootstrap_csvs()
        self.validate_task_index()
        self.validate_human_html()

    def report(self) -> int:
        for warning in self.warnings:
            print(f"WARN {warning}")
        if self.errors:
            for error in self.errors:
                print(f"FAIL {error}")
            print(f"development-agent harness failed: {len(self.errors)} error(s)")
            return 1
        print("development-agent harness passed")
        return 0


def main() -> int:
    parser = argparse.ArgumentParser(description="Validate development-agent structure and task packs.")
    parser.add_argument("--repo-root", default=".", help="Repository root. Default: current directory.")
    parser.add_argument("--task-dir", action="append", default=[], help="Task evidence directory to validate.")
    args = parser.parse_args()

    repo_root = Path(args.repo_root).resolve()
    harness = Harness(repo_root)
    harness.run_bootstrap()
    for task_dir in args.task_dir:
        harness.validate_task_dir(Path(task_dir))
    return harness.report()


if __name__ == "__main__":
    raise SystemExit(main())
