from __future__ import annotations

import asyncio
from dataclasses import dataclass
from pathlib import Path

from openclaw_lite.tools.project_upgrade_tools import (
    ProjectUpgradeError,
    append_project_bundle_handler,
    build_tools,
    read_project_bundle_handler,
    resolve_project_path,
    write_project_bundle_handler,
)


@dataclass
class _ConfigManager:
    project_root: str
    backups_dir: str

    def get(self, key: str):
        if key == "project_root":
            return self.project_root
        if key == "backups_dir":
            return self.backups_dir
        return None


@dataclass
class _Runtime:
    config_manager: _ConfigManager


@dataclass
class _Session:
    security_mode: str = "safe"


@dataclass
class _Context:
    runtime: _Runtime
    config: dict
    session: _Session


def _context(tmp_path: Path) -> _Context:
    project_root = tmp_path / "project"
    backups_dir = tmp_path / "backups"
    project_root.mkdir()
    backups_dir.mkdir()
    return _Context(
        runtime=_Runtime(_ConfigManager(str(project_root), str(backups_dir))),
        config={"project_root": str(project_root)},
        session=_Session(),
    )


def test_resolve_project_path_rejects_escape(tmp_path: Path) -> None:
    context = _context(tmp_path)
    try:
        resolve_project_path(context, "../outside.txt")
    except ProjectUpgradeError as exc:
        assert "escapes project root" in str(exc)
    else:
        raise AssertionError("Expected ProjectUpgradeError")


def test_write_and_read_project_bundle(tmp_path: Path) -> None:
    context = _context(tmp_path)
    result = asyncio.run(
        write_project_bundle_handler(
            context,
            {
                "files": [
                    {"path": "a.txt", "content": "hello"},
                    {"path": "nested/b.txt", "content": "world"},
                ]
            },
        )
    )
    assert len(result["written"]) == 2
    bundle = asyncio.run(
        read_project_bundle_handler(context, {"paths": ["a.txt", "nested/b.txt"]})
    )
    assert bundle["files"][0]["content"] == "hello"
    assert bundle["files"][1]["content"] == "world"


def test_append_project_bundle_creates_backups(tmp_path: Path) -> None:
    context = _context(tmp_path)
    asyncio.run(write_project_bundle_handler(context, {"files": [{"path": "a.txt", "content": "one"}]}))
    result = asyncio.run(
        append_project_bundle_handler(
            context,
            {"files": [{"path": "a.txt", "content": "\ntwo"}]},
        )
    )
    assert result["appended"][0]["backup"] is not None
    bundle = asyncio.run(read_project_bundle_handler(context, {"paths": ["a.txt"]}))
    assert bundle["files"][0]["content"] == "one\ntwo"


def test_build_tools_exposes_bundle_operations() -> None:
    names = [tool.name for tool in build_tools()]
    assert names == ["project_read_bundle", "project_write_bundle", "project_append_bundle"]
