"""Tests for .opencode/tools/create-readme.ts — the create-readme custom tool.

Mirrors tests/test_create_pr_tool.py / test_commit_tool.py: exercises the
tool's ``execute()`` function via ``tests/_ts_loader.mjs`` using the
``exec_stub_json`` mode (multi-arg tools).

The loader is parameterized via the ``TS_FILE`` env var. These tests set
``TS_FILE=.opencode/tools/create-readme.ts``.

Modes used:
- ``load`` — sanity-check that the tool loads and declares repo_name,
  tagline_en/ru, features_en/ru, repo, file_path args.
- ``exec_stub_json`` — call execute with a stubbed spawnSync to verify:
  (a) local create: writes README to file_path (absolute path in tmp dir),
      overwriting existing content — regression for issue #148 (create mode
      did not overwrite existing local README because file_path was resolved
      against the plugin process CWD, not context.worktree).
  (b) local create: creates a new file when none exists.
  (c) remote create: 2 spawnSync calls (GET sha + PUT), returns API message.
  (d) validation errors: missing required args, bad repo_name, latin tagline_ru.

Note: the ``validate`` mode was removed in issue #243 — README validation now
lives exclusively in ``project-status.py:check_readme`` (read-only oracle).
These tests cover only the ``create`` mode.

Regression note (issue #148): the local mode previously called
``writeFileSync(file_path, ...)`` / ``readFileSync(file_path, ...)`` with a
relative ``file_path`` (default ``"README.md"``) that resolved against the
plugin process CWD rather than ``context.worktree``. The fix wraps the path
via ``path.resolve(context.worktree, file_path)``. These tests use an
absolute ``file_path`` (in a tmp dir) so the write/read target is unambiguous;
the ``worktree`` passed by the loader is ``REPO_ROOT`` (hardcoded in
_ts_loader.mjs), so a relative path would resolve to REPO_ROOT/README.md and
clobber the repo's own README — tests avoid that by passing absolute paths.
"""

import json
import os
import subprocess
import tempfile
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parent.parent
LOADER = REPO_ROOT / "tests" / "_ts_loader.mjs"
TS_FILE = REPO_ROOT / ".opencode" / "tools" / "create-readme.ts"
TS_FILE_REL = ".opencode/tools/create-readme.ts"

VALID_CREATE_ARGS = {
    "repo_name": "test-repo",
    "tagline_en": "One-line tagline.",
    "tagline_ru": "Короткий теглайн.",
    "why_en": "Why this exists.",
    "what_en": "What it does.",
    "why_ru": "Зачем этот проект.",
    "what_ru": "Что делает.",
    "quick_start": "pip install -r requirements.txt",
    "features_en": [{"emoji": "X", "name": "Feat", "description": "desc"}],
    "features_ru": [{"emoji": "X", "name": "Фича", "description": "описание"}],
}

VALID_README = """# 🚀 test-repo

![Cover](assets/cover.png)

<!-- tagline-en:start -->
> One tagline.
<!-- tagline-en:end -->
<!-- tagline-ru:start -->
> Короткий теглайн.
<!-- tagline-ru:end -->

[English](#-english) | [Русский](#-русский)

---

## 🇺🇸 English

<!-- summary-en:start -->
### ❓ Why
Why.

### ✅ What
What.
<!-- summary-en:end -->

<!-- features-en:start -->
### Features

| Feature | Description |
|---------|-------------|
| X Foo | desc |
<!-- features-en:end -->

### ⚡ Quick Start
```bash
pip install -r requirements.txt
```
---

## 🇷🇺 Русский

<!-- summary-ru:start -->
### ❓ Зачем
Зачем.

### ✅ Что
Что.
<!-- summary-ru:end -->

<!-- features-ru:start -->
### Фичи

| Фича | Описание |
|------|----------|
| X Фича | описание |
<!-- features-ru:end -->

### ⚡ Быстрый старт
```bash
pip install -r requirements.txt
```
---

## 💬 Support and contacts / Поддержка и контакты

👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)**
"""

DELIMITERS = [
    "tagline-en:start",
    "tagline-en:end",
    "tagline-ru:start",
    "tagline-ru:end",
    "summary-en:start",
    "summary-en:end",
    "features-en:start",
    "features-en:end",
    "summary-ru:start",
    "summary-ru:end",
    "features-ru:start",
    "features-ru:end",
]


def _run_loader(*args: str) -> dict:
    """Invoke the loader with TS_FILE env set to create-readme.ts and parse JSON stdout."""
    env = {**os.environ, "TS_FILE": TS_FILE_REL}
    proc = subprocess.run(
        ["node", str(LOADER), *args],
        capture_output=True,
        text=True,
        check=False,
        cwd=str(REPO_ROOT),
        timeout=60,
        env=env,
    )
    if proc.returncode != 0:
        raise RuntimeError(
            f"_ts_loader.mjs {' '.join(args)} failed (exit {proc.returncode}):\n"
            f"stdout: {proc.stdout}\nstderr: {proc.stderr}"
        )
    return json.loads(proc.stdout)


def _run_exec(args: dict, responses: list[dict]) -> dict:
    """Helper: exec_stub_json mode with JSON args + sequential stub responses."""
    return _run_loader("exec_stub_json", json.dumps(args), json.dumps(responses))


def _valid_args(file_path: str) -> dict:
    """Return a copy of VALID_CREATE_ARGS with file_path set (local mode)."""
    return {**VALID_CREATE_ARGS, "file_path": file_path}


def test_loader_can_load_tool():
    """Sanity: create-readme.ts loads and declares the expected arguments."""
    if not TS_FILE.exists():
        pytest.skip("create-readme.ts not present")
    out = _run_loader("load")
    assert "description" in out
    args = out["args"]
    for key in (
        "repo_name",
        "tagline_en",
        "tagline_ru",
        "why_en",
        "what_en",
        "why_ru",
        "what_ru",
        "features_en",
        "features_ru",
        "repo",
        "file_path",
    ):
        assert key in args, f"missing {key} arg: {args}"
    assert "mode" not in args, f"mode arg should be removed (validate mode dropped in #243): {args}"


def test_local_create_overwrites_existing_readme():
    """Regression #148: local create overwrites an existing README at file_path.

    A pre-existing README (non-empty, old content) must be replaced with the
    generated content: mtime updates, delimiter tags present, old content gone.
    """
    with tempfile.TemporaryDirectory() as tmp:
        readme = Path(tmp) / "README.md"
        readme.write_text("# OLD CONTENT\nshould be overwritten\n", encoding="utf-8")
        mtime_before = readme.stat().st_mtime_ns
        out = _run_exec(_valid_args(str(readme)), [])
        result = out["result"]
        assert "created at" in result, f"expected success, got: {result!r}"
        mtime_after = readme.stat().st_mtime_ns
        assert mtime_after > mtime_before, (
            f"mtime did not advance: before={mtime_before} after={mtime_after}"
        )
        content = readme.read_text(encoding="utf-8")
        assert "# OLD CONTENT" not in content, "old content survived — overwrite failed"
        for d in DELIMITERS:
            tag = f"<!-- {d} -->"
            assert tag in content, f"missing delimiter {tag} in generated README"
        assert "<!-- summary-en:start -->" in content


def test_local_create_creates_new_readme():
    """Local create writes a new README when file_path does not exist yet."""
    with tempfile.TemporaryDirectory() as tmp:
        readme = Path(tmp) / "README.md"
        assert not readme.exists()
        out = _run_exec(_valid_args(str(readme)), [])
        result = out["result"]
        assert "created at" in result, f"expected success, got: {result!r}"
        assert readme.exists(), "README was not created"
        content = readme.read_text(encoding="utf-8")
        assert "# 🚀 test-repo" in content
        assert "assets/cover.png" in content


def test_local_create_custom_subdir_file_path():
    """Local create writes to a nested file_path (subdir must be auto-created?).

    Note: writeFileSync does NOT create parent dirs. This test confirms the
    tool writes successfully when the parent directory exists (tmp subdir).
    """
    with tempfile.TemporaryDirectory() as tmp:
        subdir = Path(tmp) / "docs"
        subdir.mkdir()
        readme = subdir / "README.md"
        out = _run_exec(_valid_args(str(readme)), [])
        result = out["result"]
        assert "created at" in result, f"expected success, got: {result!r}"
        assert readme.exists()


def test_remote_create_makes_two_spawnsync_calls():
    """Remote create (args.repo set) makes GET (sha) + PUT via gh api.

    Regression guard: the remote path must not be broken by the local-mode
    path.resolve fix. Stub returns sha on GET, {} on PUT.
    """
    args = {**VALID_CREATE_ARGS, "repo": "slaid098/test-repo"}
    responses = [
        {"status": 0, "stdout": json.dumps({"sha": "abc123"}), "stderr": ""},
        {"status": 0, "stdout": "{}", "stderr": ""},
    ]
    out = _run_exec(args, responses)
    result = out["result"]
    assert "updated in slaid098/test-repo via GitHub API" in result, (
        f"expected remote success, got: {result!r}"
    )
    calls = out["calls"]
    assert len(calls) == 2, f"expected 2 gh api calls, got {len(calls)}"
    assert calls[0]["args"][1] == "repos/slaid098/test-repo/contents/README.md"
    assert calls[1]["args"][2] == "PUT"


def test_remote_create_passes_worktree_cwd():
    """Remote create spawnSync opts carry cwd=context.worktree (ADR-023)."""
    args = {**VALID_CREATE_ARGS, "repo": "slaid098/test-repo"}
    responses = [
        {"status": 0, "stdout": json.dumps({"sha": "abc123"}), "stderr": ""},
        {"status": 0, "stdout": "{}", "stderr": ""},
    ]
    out = _run_exec(args, responses)
    for call in out["calls"]:
        opts = call["opts"]
        assert opts is not None, "spawnSync called without opts — expected cwd"
        assert "cwd" in opts, f"opts missing cwd key: {opts}"
        assert opts["cwd"] == str(REPO_ROOT), (
            f"cwd must equal context.worktree ({REPO_ROOT}), got {opts['cwd']!r}"
        )


def test_create_missing_required_repo_name():
    """create without repo_name → error mentioning required."""
    args = {**VALID_CREATE_ARGS}
    del args["repo_name"]
    with tempfile.TemporaryDirectory() as tmp:
        args["file_path"] = str(Path(tmp) / "README.md")
        out = _run_exec(args, [])
        result = out["result"]
        assert "repo_name is required" in result, f"expected required error, got: {result!r}"


def test_create_missing_required_tagline_ru():
    """create without tagline_ru → error mentioning required."""
    args = {**VALID_CREATE_ARGS}
    del args["tagline_ru"]
    with tempfile.TemporaryDirectory() as tmp:
        args["file_path"] = str(Path(tmp) / "README.md")
        out = _run_exec(args, [])
        result = out["result"]
        assert "tagline_ru is required" in result, f"expected required error, got: {result!r}"


def test_create_bad_repo_name_uppercase():
    """create with uppercase repo_name → kebab-case validation error."""
    with tempfile.TemporaryDirectory() as tmp:
        args = {
            **VALID_CREATE_ARGS,
            "repo_name": "TestRepo",
            "file_path": str(Path(tmp) / "README.md"),
        }
        out = _run_exec(args, [])
        result = out["result"]
        assert "kebab-case" in result, f"expected kebab-case error, got: {result!r}"


def test_create_latin_tagline_ru():
    """create with latin-only tagline_ru → Cyrillic requirement error."""
    with tempfile.TemporaryDirectory() as tmp:
        args = {
            **VALID_CREATE_ARGS,
            "tagline_ru": "latin only",
            "file_path": str(Path(tmp) / "README.md"),
        }
        out = _run_exec(args, [])
        result = out["result"]
        assert "Cyrillic" in result, f"expected Cyrillic error, got: {result!r}"


def test_create_cyrillic_tagline_en():
    """create with Cyrillic tagline_en → English-only requirement error."""
    with tempfile.TemporaryDirectory() as tmp:
        args = {
            **VALID_CREATE_ARGS,
            "tagline_en": "кириллица тут",
            "file_path": str(Path(tmp) / "README.md"),
        }
        out = _run_exec(args, [])
        result = out["result"]
        assert "no Cyrillic" in result, f"expected no-Cyrillic error, got: {result!r}"


def test_create_missing_features_en():
    """create with empty features_en → error mentioning required."""
    with tempfile.TemporaryDirectory() as tmp:
        args = {**VALID_CREATE_ARGS, "features_en": [], "file_path": str(Path(tmp) / "README.md")}
        out = _run_exec(args, [])
        result = out["result"]
        assert "features_en is required" in result, f"expected required error, got: {result!r}"
