"""Tests for timmy.workspace — Workspace heartbeat monitoring."""

from timmy.workspace import WorkspaceMonitor

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_monitor(tmp_path, monkeypatch):
    """Create a WorkspaceMonitor with tmp_path as the repo_root."""
    # Mock repo_root to use tmp_path
    monkeypatch.setattr(
        "timmy.workspace.settings", type("obj", (object,), {"repo_root": str(tmp_path)})()
    )

    state_path = tmp_path / "workspace_state.json"
    return WorkspaceMonitor(state_path=state_path)


def _setup_workspace(tmp_path):
    """Create the workspace directory structure."""
    workspace = tmp_path / "workspace"
    workspace.mkdir()
    (workspace / "inbox").mkdir()
    (workspace / "outbox").mkdir()
    return workspace


# ---------------------------------------------------------------------------
# Basic monitoring
# ---------------------------------------------------------------------------


def test_no_updates_when_empty(tmp_path, monkeypatch):
    """Fresh monitor with no workspace files should report no updates."""
    # Don't create workspace dir — monitor should handle gracefully
    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()

    assert updates["new_correspondence"] is None
    assert updates["new_inbox_files"] == []


def test_detects_new_correspondence(tmp_path, monkeypatch):
    """Writing to correspondence.md should be detected as new entries."""
    workspace = _setup_workspace(tmp_path)
    correspondence = workspace / "correspondence.md"

    # Pre-populate correspondence file
    correspondence.write_text("Entry 1\nEntry 2\nEntry 3\n")

    monitor = _make_monitor(tmp_path, monkeypatch)

    # Should detect all 3 lines as new
    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] == "Entry 1\nEntry 2\nEntry 3"
    assert updates["new_inbox_files"] == []


def test_detects_new_inbox_file(tmp_path, monkeypatch):
    """Creating a file in inbox/ should be detected as new."""
    workspace = _setup_workspace(tmp_path)
    inbox = workspace / "inbox"

    # Create a file in inbox
    (inbox / "message_1.md").write_text("Hello Timmy")

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] is None
    assert updates["new_inbox_files"] == ["message_1.md"]


def test_detects_multiple_inbox_files(tmp_path, monkeypatch):
    """Multiple new inbox files should all be detected."""
    workspace = _setup_workspace(tmp_path)
    inbox = workspace / "inbox"

    # Create multiple files
    (inbox / "message_2.md").write_text("Hello again")
    (inbox / "task_1.md").write_text("Do something")
    (inbox / "note.txt").write_text("A note")

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_inbox_files"] == ["message_2.md", "note.txt", "task_1.md"]


def test_detects_both_correspondence_and_inbox(tmp_path, monkeypatch):
    """Monitor should detect both correspondence and inbox updates together."""
    workspace = _setup_workspace(tmp_path)

    correspondence = workspace / "correspondence.md"
    correspondence.write_text("New journal entry\n")

    inbox = workspace / "inbox"
    (inbox / "urgent.md").write_text("Urgent message")

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] == "New journal entry"
    assert updates["new_inbox_files"] == ["urgent.md"]


# ---------------------------------------------------------------------------
# Marking as seen
# ---------------------------------------------------------------------------


def test_mark_seen_clears_pending(tmp_path, monkeypatch):
    """After mark_seen, get_pending_updates should return empty."""
    workspace = _setup_workspace(tmp_path)

    correspondence = workspace / "correspondence.md"
    correspondence.write_text("Line 1\nLine 2\n")

    inbox = workspace / "inbox"
    (inbox / "file.md").write_text("Content")

    monitor = _make_monitor(tmp_path, monkeypatch)

    # First check — should have updates
    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] is not None
    assert len(updates["new_inbox_files"]) == 1

    # Mark as seen
    monitor.mark_seen()

    # Second check — should be empty
    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] is None
    assert updates["new_inbox_files"] == []


def test_mark_seen_persists_line_count(tmp_path, monkeypatch):
    """mark_seen should remember how many lines we've seen."""
    workspace = _setup_workspace(tmp_path)

    correspondence = workspace / "correspondence.md"
    correspondence.write_text("Line 1\nLine 2\nLine 3\n")

    monitor = _make_monitor(tmp_path, monkeypatch)
    monitor.mark_seen()

    # Add more lines
    correspondence.write_text("Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n")

    # Should only see the new lines
    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] == "Line 4\nLine 5"


# ---------------------------------------------------------------------------
# State persistence
# ---------------------------------------------------------------------------


def test_state_persists_across_instances(tmp_path, monkeypatch):
    """State should be saved and loaded when creating a new monitor instance."""
    workspace = _setup_workspace(tmp_path)

    correspondence = workspace / "correspondence.md"
    correspondence.write_text("First entry\n")

    inbox = workspace / "inbox"
    (inbox / "first.md").write_text("First")

    # First monitor instance
    monitor1 = _make_monitor(tmp_path, monkeypatch)
    monitor1.mark_seen()

    # Add new content
    correspondence.write_text("First entry\nSecond entry\n")
    (inbox / "second.md").write_text("Second")

    # Second monitor instance (should load state from file)
    monitor2 = _make_monitor(tmp_path, monkeypatch)

    updates = monitor2.get_pending_updates()
    assert updates["new_correspondence"] == "Second entry"
    assert updates["new_inbox_files"] == ["second.md"]


def test_state_survives_missing_files(tmp_path, monkeypatch):
    """Monitor should handle missing correspondence file gracefully."""
    workspace = _setup_workspace(tmp_path)

    # Create and mark as seen
    correspondence = workspace / "correspondence.md"
    correspondence.write_text("Entry\n")

    monitor = _make_monitor(tmp_path, monkeypatch)
    monitor.mark_seen()

    # Delete the file
    correspondence.unlink()

    # Should return None gracefully
    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] is None


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------


def test_empty_correspondence_file(tmp_path, monkeypatch):
    """Empty correspondence file should return None."""
    workspace = _setup_workspace(tmp_path)
    (workspace / "correspondence.md").write_text("")

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] is None


def test_empty_inbox_dir(tmp_path, monkeypatch):
    """Empty inbox directory should return empty list."""
    _setup_workspace(tmp_path)

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_inbox_files"] == []


def test_missing_inbox_dir(tmp_path, monkeypatch):
    """Missing inbox directory should return empty list."""
    workspace = tmp_path / "workspace"
    workspace.mkdir()
    # No inbox subdir

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_inbox_files"] == []


def test_missing_workspace_dir(tmp_path, monkeypatch):
    """Missing workspace directory should return empty results."""
    # No workspace dir at all
    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] is None
    assert updates["new_inbox_files"] == []


def test_correspondence_with_blank_lines(tmp_path, monkeypatch):
    """Correspondence with blank lines should be handled correctly."""
    workspace = _setup_workspace(tmp_path)

    correspondence = workspace / "correspondence.md"
    correspondence.write_text("Entry 1\n\nEntry 2\n\n\nEntry 3\n")

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] == "Entry 1\n\nEntry 2\n\n\nEntry 3"


def test_inbox_ignores_subdirectories(tmp_path, monkeypatch):
    """Inbox should only list files, not subdirectories."""
    workspace = _setup_workspace(tmp_path)
    inbox = workspace / "inbox"

    (inbox / "file.md").write_text("Content")
    (inbox / "subdir").mkdir()

    monitor = _make_monitor(tmp_path, monkeypatch)

    updates = monitor.get_pending_updates()
    assert updates["new_inbox_files"] == ["file.md"]


def test_deleted_inbox_files_removed_from_state(tmp_path, monkeypatch):
    """When inbox files are deleted, they should be removed from seen list."""
    workspace = _setup_workspace(tmp_path)
    inbox = workspace / "inbox"

    # Create and see a file
    (inbox / "temp.md").write_text("Temp")
    monitor = _make_monitor(tmp_path, monkeypatch)
    monitor.mark_seen()

    # Delete the file
    (inbox / "temp.md").unlink()

    # mark_seen should update seen list to remove deleted files
    monitor.mark_seen()

    # State should now have empty seen list
    assert monitor._state["seen_inbox_files"] == []


def test_correspondence_append_only(tmp_path, monkeypatch):
    """Correspondence is append-only; modifying existing content doesn't re-notify."""
    workspace = _setup_workspace(tmp_path)

    correspondence = workspace / "correspondence.md"
    correspondence.write_text("Line 1\nLine 2\n")

    monitor = _make_monitor(tmp_path, monkeypatch)
    monitor.mark_seen()

    # Modify the file (truncate and rewrite) — this resets line count
    # but correspondence.md should be append-only in practice
    correspondence.write_text("Modified Line 1\nModified Line 2\nLine 3\n")

    # Line 3 is the only truly new line (we've now seen 2, file has 3)
    updates = monitor.get_pending_updates()
    assert updates["new_correspondence"] == "Line 3"
