from pathlib import Path
from unittest.mock import Mock

import pytest
from fastapi.testclient import TestClient

from dual_agent.adapters.base import Reply
from dual_agent.api import create_app
from dual_agent.domain import ImplementationArtifact, TaskState
from dual_agent.state_machine import InvalidTransition
from tests._fakes import fake_orchestrator, task_in_state


def completed_task(orc):
    task = task_in_state(orc, "INIT")
    Path(task.worktree_path, "delivery.txt").write_text("delivered\n", encoding="utf-8")
    orc.store.write_artifact(task.task_id, "impl_report.json", ImplementationArtifact(
        files_changed=["delivery.txt"], tests_run=[], notes="delivery",
    ))
    return orc._save(task, state=TaskState.DONE)


def test_completed_task_can_discuss_then_reopen_preserving_delivery_and_history():
    orc = fake_orchestrator()
    task = completed_task(orc)
    orc.say(task.task_id, "修复保存按钮")
    orc.planner.ask = Mock(return_value=Reply(ok=True, text="需要修复保存按钮事件。"))
    result = orc.discuss(task.task_id)
    assert result.state is TaskState.DONE
    assert orc.conversation(task.task_id).all()[-1].text == "需要修复保存按钮事件。"
    assert "修复保存按钮" in orc.planner.ask.call_args.args[0]
    previous_baseline = task.baseline_commit
    directory = orc.store.task_dir(task.task_id)
    result = orc.reopen(task.task_id)
    assert result.state is TaskState.PLAN
    assert result.baseline_commit == previous_baseline
    assert Path(task.worktree_path, "delivery.txt").read_text() == "delivered\n"
    assert not (directory / "impl_report.json").exists()
    assert list((directory / "revisions").glob("*/impl_report.json"))
    assert list((directory / "revisions").glob("*/state.json"))
    assert "修复保存按钮" in orc.conversation(task.task_id).guidance()
    assert result.session_ids == {}
    orc.conversation(task.task_id).mark_delivered()
    orc._save(result, state=TaskState.DONE)
    with pytest.raises(InvalidTransition):
        orc.reopen(task.task_id)



def test_stalled_task_can_discuss_and_replan_but_cancelled_task_cannot():
    from dual_agent.domain import ErrorCategory
    orc = fake_orchestrator()
    task = completed_task(orc)
    task = orc._save(task, state=TaskState.NEEDS_HUMAN, error_category=ErrorCategory.STALLED,
                     error="no delivery", constraints=["planning only"])
    orc.say(task.task_id, "现在进入实现阶段，修复保存按钮")
    orc.planner.ask = Mock(return_value=Reply(text="可以重新规划。"))
    assert orc.discuss(task.task_id).state is TaskState.NEEDS_HUMAN
    restarted = orc.reopen(task.task_id)
    assert restarted.state is TaskState.PLAN
    assert restarted.constraints == ["planning only"]
    assert restarted.error is None
    orc._save(restarted, state=TaskState.NEEDS_HUMAN, error_category=ErrorCategory.OPERATOR)
    with pytest.raises(InvalidTransition):
        orc.discuss(task.task_id)
    with pytest.raises(InvalidTransition):
        orc.reopen(task.task_id)


def test_reopen_requires_feedback_and_completed_state():
    orc = fake_orchestrator()
    task = completed_task(orc)
    with pytest.raises(InvalidTransition):
        orc.reopen(task.task_id)
    assert orc.get_task(task.task_id).state is TaskState.DONE
    task = task_in_state(orc, "PLAN")
    with pytest.raises(InvalidTransition):
        orc.reopen(task.task_id)


def test_follow_up_endpoints_and_reopen_retry_are_idempotent():
    orc = fake_orchestrator()
    task = completed_task(orc)
    client = TestClient(create_app(orc))
    assert client.post(f"/tasks/{task.task_id}/reopen").status_code == 409
    assert client.post(f"/tasks/{task.task_id}/say", json={"text": "修复按钮"}).status_code == 200
    orc.planner.ask = Mock(return_value=Reply(ok=True, text="建议修复点击处理。"))
    headers = {"X-Request-Id": "follow-up-discuss"}
    assert client.post(f"/tasks/{task.task_id}/discuss", headers=headers).status_code == 200
    assert client.post(f"/tasks/{task.task_id}/discuss", headers=headers).status_code == 200
    assert orc.planner.ask.call_count == 1
    headers = {"X-Request-Id": "follow-up-reopen"}
    first = client.post(f"/tasks/{task.task_id}/reopen", headers=headers)
    second = client.post(f"/tasks/{task.task_id}/reopen", headers=headers)
    assert first.status_code == second.status_code == 200
    assert first.json()["state"] == second.json()["state"] == "PLAN"
    assert len(list((orc.store.task_dir(task.task_id) / "revisions").iterdir())) == 1
