import io
from pathlib import Path

from cotext_delta.approval import ApprovalOptions
from cotext_delta.chat_session import ChatSession
from cotext_delta.llm_client import LLMResult, ToolCall
from cotext_delta.logger import Logger
from cotext_delta.plan import PlanContext
from cotext_delta.tool_registry import ToolRegistry


class FakeLLM:
    def __init__(self, results):
        self.results = results
        self.calls = 0

    def invoke(self, messages, tools=None):
        result = self.results[self.calls]
        self.calls += 1
        return result


def make_logger():
    return Logger(stream_info=io.StringIO(), stream_error=io.StringIO())


def test_chat_session_simple_response():
    fake = FakeLLM([LLMResult(content="hi", tool_calls=[])])
    session = ChatSession(fake, registry=ToolRegistry(logger=make_logger()), logger=make_logger(), approval=ApprovalOptions(assume_yes=True))
    out = session.run_turn("hello")
    assert out == "hi"
    assert fake.calls == 1


def test_chat_session_tool_call(tmp_path):
    file_path = Path(tmp_path / "f.txt")
    file_path.write_text("line1\nline2\n", encoding="utf-8")
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="read_file",
                        arguments={"file": str(file_path), "offset": 1, "limit": 1},
                    )
                ],
            ),
            LLMResult(content="done", tool_calls=[]),
        ]
    )
    session = ChatSession(fake, registry=ToolRegistry(logger=make_logger()), logger=make_logger(), approval=ApprovalOptions(assume_yes=True))
    out = session.run_turn("read it")
    assert out == "done"
    assert fake.calls == 2


def test_chat_session_tool_call_missing_dir_defaults_cwd(monkeypatch, tmp_path):
    # Ensure cwd is tmp_path for deterministic listing
    monkeypatch.chdir(tmp_path)
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="list_dir",
                        arguments={},
                    )
                ],
            ),
            LLMResult(content="ok", tool_calls=[]),
        ]
    )
    session = ChatSession(fake, registry=ToolRegistry(logger=make_logger()), logger=make_logger(), approval=ApprovalOptions(assume_yes=True))
    out = session.run_turn("list cwd")
    assert out == "ok"
    assert fake.calls == 2


def test_chat_session_tool_followup_empty_returns_tool_output(tmp_path):
    file_path = Path(tmp_path / "f.txt")
    file_path.write_text("line1\n", encoding="utf-8")
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="read_file",
                        arguments={"file": str(file_path), "offset": 1, "limit": 1},
                    )
                ],
            ),
            LLMResult(content="", tool_calls=[]),
        ]
    )
    session = ChatSession(fake, registry=ToolRegistry(logger=make_logger()), logger=make_logger(), approval=ApprovalOptions(assume_yes=True))
    out = session.run_turn("read it")
    assert "line1" in out


def test_chat_session_followup_tool_call_executes(tmp_path):
    file_path = Path(tmp_path / "f.txt")
    file_path.write_text("line1\n", encoding="utf-8")
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="list_dir",
                        arguments={},
                    )
                ],
            ),
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="2",
                        name="read_file",
                        arguments={"file": str(file_path), "offset": 1, "limit": 1},
                    )
                ],
            ),
        ]
    )
    session = ChatSession(fake, registry=ToolRegistry(logger=make_logger()), logger=make_logger(), approval=ApprovalOptions(assume_yes=True))
    out = session.run_turn("read via followup")
    assert "line1" in out
    assert fake.calls == 2


def test_chat_session_tool_call_relative_path(tmp_path, monkeypatch):
    file_path = tmp_path / "rel.txt"
    file_path.write_text("hi", encoding="utf-8")
    monkeypatch.chdir(tmp_path)
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="read_file",
                        arguments={"file": "rel.txt", "offset": 1, "limit": 1},
                    )
                ],
            ),
            LLMResult(content="ok", tool_calls=[]),
        ]
    )
    session = ChatSession(fake, registry=ToolRegistry(logger=make_logger()), logger=make_logger(), approval=ApprovalOptions(assume_yes=True))
    out = session.run_turn("read relative")
    assert "hi" in out or out == "ok"
    assert fake.calls == 2


def test_chat_session_plan_guard_stops_after_max_steps(tmp_path):
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="update_plan",
                        arguments={"plan": [{"step": "a", "status": "completed"}]},
                    )
                ],
            )
        ]
    )
    plan_ctx = PlanContext({"plan": [{"step": "a", "status": "pending"}]})
    session = ChatSession(
        fake,
        registry=ToolRegistry(logger=make_logger()),
        logger=make_logger(),
        approval=ApprovalOptions(assume_yes=True),
        plan_context=plan_ctx,
        max_steps=1,
    )
    out = session.run_turn("run plan")
    assert "completed" in out or out  # summary returned
    assert fake.calls == 1


def test_chat_session_plan_update(tmp_path):
    fake = FakeLLM(
        [
            LLMResult(
                content="",
                tool_calls=[
                    ToolCall(
                        id="1",
                        name="update_plan",
                        arguments={"plan": [{"step": "a", "status": "completed"}]},
                    )
                ],
            ),
            LLMResult(content="done", tool_calls=[]),
        ]
    )
    plan_ctx = PlanContext({"plan": [{"step": "a", "status": "pending"}]})
    session = ChatSession(
        fake,
        registry=ToolRegistry(logger=make_logger()),
        logger=make_logger(),
        approval=ApprovalOptions(assume_yes=True),
        plan_context=plan_ctx,
    )
    out = session.run_turn("run plan")
    assert fake.calls == 2
    assert plan_ctx.data["plan"][0]["status"] == "completed"
    assert out in ("done", "") or "completed" in out
