import pytest
from unittest.mock import MagicMock, patch

import notilens
from notilens import NotiLensAgent


@pytest.fixture(autouse=True)
def reset_agents():
    notilens._agents.clear()
    notilens._patch_owner = None
    yield
    notilens._agents.clear()
    notilens._patch_owner = None


@pytest.fixture
def agent(monkeypatch, tmp_path):
    monkeypatch.setattr("notilens.config.CONFIG_FILE", tmp_path / ".notilens_config.json")
    a = notilens.init(agent="test-agent", token="tok", secret="sec")
    a._emitter._deliver = MagicMock()  # prevent real HTTP
    return a


def test_init_returns_agent(agent):
    assert isinstance(agent, NotiLensAgent)
    assert agent.agent == "test-agent"


def test_init_reads_env_vars(monkeypatch, tmp_path):
    monkeypatch.setattr("notilens.config.CONFIG_FILE", tmp_path / ".notilens_config.json")
    monkeypatch.setenv("NOTILENS_TOKEN", "env-tok")
    monkeypatch.setenv("NOTILENS_SECRET", "env-sec")
    a = notilens.init(agent="env-agent")
    assert a._emitter.token == "env-tok"
    a.shutdown()


def test_init_raises_without_credentials(monkeypatch):
    monkeypatch.delenv("NOTILENS_TOKEN", raising=False)
    monkeypatch.delenv("NOTILENS_SECRET", raising=False)
    with pytest.raises(ValueError, match="token and secret"):
        notilens.init(agent="bad-agent")


def test_task_lifecycle(agent, tmp_path, monkeypatch):
    monkeypatch.setattr("notilens.state.get_state_file",
                        lambda ag, tid: tmp_path / f"{ag}_{tid}.json")
    sent = []
    agent._emitter.send = lambda *a, **kw: sent.append((a, kw))

    task_id = agent.task_start(task_id="t1")
    assert task_id == "t1"

    agent.task_progress("step 1", task_id="t1")
    agent.task_loop("loop 1", task_id="t1")
    agent.task_retry(task_id="t1")
    agent.task_complete("done", task_id="t1")

    events = [s[0][0] for s in sent]
    assert "task.started" in events
    assert "task.progress" in events
    assert "task.loop" in events
    assert "task.retrying" in events
    assert "task.completed" in events


def test_task_fail_deletes_state(agent, tmp_path, monkeypatch):
    sf = tmp_path / "agent_t2.json"
    monkeypatch.setattr("notilens.get_state_file", lambda ag, tid: sf)
    agent._emitter.send = MagicMock()

    from notilens.state import write_state
    write_state(sf, {"start_time": 0})

    agent.task_fail("failed", task_id="t2")
    assert not sf.exists()


def test_task_complete_deletes_state(agent, tmp_path, monkeypatch):
    sf = tmp_path / "agent_t3.json"
    monkeypatch.setattr("notilens.get_state_file", lambda ag, tid: sf)
    agent._emitter.send = MagicMock()

    from notilens.state import write_state
    write_state(sf, {"start_time": 0})

    agent.task_complete("done", task_id="t3")
    assert not sf.exists()


def test_emit_custom_event(agent):
    sent = []
    agent._emitter.send = lambda *a, **kw: sent.append((a, kw))
    agent.emit("order.placed", "Order #1", meta={"amount": 99})
    assert sent[0][0][0] == "order.placed"
    assert sent[0][1]["meta"]["amount"] == 99


def test_input_events(agent):
    sent = []
    agent._emitter.send = lambda *a, **kw: sent.append(a[0])
    agent.input_required("confirm?", task_id="t1")
    agent.input_approved("yes", task_id="t1")
    agent.input_rejected("no", task_id="t1")
    assert "input.required" in sent
    assert "input.approved" in sent
    assert "input.rejected" in sent


def test_ai_response_events(agent):
    sent = []
    agent._emitter.send = lambda *a, **kw: sent.append(a[0])
    agent.ai_response_generated("summary", task_id="t1")
    agent.ai_response_failed("model error", task_id="t1")
    assert "ai.response.generated" in sent
    assert "ai.response.failed" in sent


def test_patch_owner_conflict(monkeypatch, tmp_path):
    monkeypatch.setattr("notilens.config.CONFIG_FILE", tmp_path / ".notilens_config.json")
    monkeypatch.setattr("notilens.NotiLensAgent._apply_patches", lambda self, t: [])

    a = notilens.init(agent="agent-a", token="t", secret="s", patch=True)
    a._emitter._deliver = MagicMock()

    with pytest.raises(RuntimeError, match="already claimed"):
        b = notilens.init(agent="agent-b", token="t", secret="s", patch=True)


def test_task_start_autogenerates_id(agent, tmp_path, monkeypatch):
    monkeypatch.setattr("notilens.state.get_state_file",
                        lambda ag, tid: tmp_path / f"{ag}_{tid}.json")
    agent._emitter.send = MagicMock()
    task_id = agent.task_start()
    assert task_id.startswith("task_")
