"""Tests for state persistence and recovery.

Phase 5, step 4: State file read/write, agent listing, state transitions.

Uses real tmp filesystem - no mocking state layer.
"""

import json
from pathlib import Path

import pytest
from returns.result import Failure, Success

from anima.core.schemas import (
    AgentResult,
    AgentState,
    AgentStateData,
    JobSpec,
    LifecycleType,
    TokenUsage,
)
from anima.shell.state import (
    LogTailer,
    append_log,
    create_agent_state,
    delete_state,
    get_agent_dir,
    get_state_dir,
    is_agent_process_alive,
    list_agents,
    read_log,
    read_result,
    read_state,
    reap_stale_agent,
    write_result,
    write_state,
)


@pytest.fixture
def tmp_state_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Create a temporary state directory and set ANIMA_STATE."""
    state_dir = tmp_path / "anima_state"
    state_dir.mkdir()
    monkeypatch.setenv("ANIMA_STATE", str(state_dir))
    return state_dir


@pytest.fixture
def sample_state_data() -> AgentStateData:
    """Create sample agent state data."""
    return AgentStateData(
        instance_id="inst_test123",
        trace_id="tr_test123",
        persona_name="test-persona",
        persona_ref="personas/test.yaml",
        state=AgentState.running,
        lifecycle=LifecycleType.ephemeral,
        depth=0,
        max_depth=3,
    )


class TestStatePersistence:
    """Tests for state.json read/write operations."""

    def test_write_state_creates_file_and_directory(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """write_state creates agent directory and state.json file."""
        result = write_state("inst_test123", sample_state_data)

        assert isinstance(result, Success)
        state_file = tmp_state_dir / "agents" / "inst_test123" / "state.json"
        assert state_file.exists()

        # Verify content
        content = state_file.read_text()
        data = json.loads(content)
        assert data["instance_id"] == "inst_test123"
        assert data["trace_id"] == "tr_test123"
        assert data["state"] == "running"

    def test_write_state_creates_log_entry(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """write_state appends a log entry."""
        write_state("inst_test123", sample_state_data, "test_event", {"key": "value"})

        log_file = tmp_state_dir / "agents" / "inst_test123" / "log.jsonl"
        assert log_file.exists()

        entries = log_file.read_text().strip().split("\n")
        assert len(entries) == 1
        entry = json.loads(entries[0])
        assert entry["event"] == "test_event"
        assert entry["details"]["key"] == "value"
        assert entry["details"]["state"] == "running"

    def test_read_state_returns_correct_data(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """read_state returns the correct AgentStateData."""
        write_state("inst_test123", sample_state_data)

        result = read_state("inst_test123")

        assert isinstance(result, Success)
        state_data = result.unwrap()
        assert state_data.instance_id == "inst_test123"
        assert state_data.trace_id == "tr_test123"
        assert state_data.state == AgentState.running
        assert state_data.persona_name == "test-persona"

    def test_read_state_fails_for_nonexistent_agent(self, tmp_state_dir: Path) -> None:
        """read_state returns Failure for non-existent agent."""
        result = read_state("inst_nonexistent")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "not found" in str(error).lower()

    def test_read_state_fails_for_invalid_json(self, tmp_state_dir: Path) -> None:
        """read_state returns Failure for invalid JSON."""
        agent_dir = tmp_state_dir / "agents" / "inst_bad"
        agent_dir.mkdir(parents=True)
        (agent_dir / "state.json").write_text("not valid json")

        result = read_state("inst_bad")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "invalid" in str(error).lower()

    def test_delete_state_removes_directory(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """delete_state removes the entire agent directory."""
        write_state("inst_test123", sample_state_data)
        agent_dir = tmp_state_dir / "agents" / "inst_test123"
        assert agent_dir.exists()

        result = delete_state("inst_test123")

        assert isinstance(result, Success)
        assert not agent_dir.exists()

    def test_delete_state_fails_for_nonexistent_agent(
        self, tmp_state_dir: Path
    ) -> None:
        """delete_state returns Failure for non-existent agent."""
        result = delete_state("inst_nonexistent")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "not found" in str(error).lower()

    def test_atomic_write_uses_temp_file(
        self,
        tmp_state_dir: Path,
        sample_state_data: AgentStateData,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """write_state uses atomic write pattern (write to .tmp, then rename)."""
        # Track file operations
        created_tmp = []
        original_replace = Path.replace

        def track_replace(self: Path, target: Path) -> None:
            if ".tmp" in str(self):
                created_tmp.append(self)
            original_replace(self, target)

        monkeypatch.setattr(Path, "replace", track_replace)

        write_state("inst_test123", sample_state_data)

        # Verify .tmp was created and renamed
        assert len(created_tmp) == 1
        state_file = tmp_state_dir / "agents" / "inst_test123" / "state.json"
        assert state_file.exists()
        # Temp file should be gone after atomic rename
        assert not created_tmp[0].exists()

    def test_write_state_overwrites_existing(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """write_state overwrites existing state file."""
        write_state("inst_test123", sample_state_data)

        # Update state
        sample_state_data.state = AgentState.completed
        write_state("inst_test123", sample_state_data)

        result = read_state("inst_test123")
        assert isinstance(result, Success)
        assert result.unwrap().state == AgentState.completed


class TestAgentListing:
    """Tests for agent listing and filtering."""

    def test_list_agents_returns_all_agents(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """list_agents returns all agents without filter."""
        # Create multiple agents
        for i in range(3):
            data = AgentStateData(
                instance_id=f"inst_{i}",
                trace_id=f"tr_{i}",
                persona_name=f"persona_{i}",
            )
            write_state(f"inst_{i}", data)

        result = list_agents()

        assert isinstance(result, Success)
        agents = result.unwrap()
        assert len(agents) == 3
        instance_ids = {a.instance_id for a in agents}
        assert instance_ids == {"inst_0", "inst_1", "inst_2"}

    def test_list_agents_filters_by_state(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """list_agents filters agents by state."""
        # Create agents in different states
        running = AgentStateData(
            instance_id="inst_running",
            trace_id="tr_running",
            persona_name="running",
            state=AgentState.running,
        )
        completed = AgentStateData(
            instance_id="inst_completed",
            trace_id="tr_completed",
            persona_name="completed",
            state=AgentState.completed,
        )
        write_state("inst_running", running)
        write_state("inst_completed", completed)

        result_running = list_agents(state_filter=AgentState.running)
        result_completed = list_agents(state_filter=AgentState.completed)

        assert isinstance(result_running, Success)
        assert isinstance(result_completed, Success)
        assert len(result_running.unwrap()) == 1
        assert result_running.unwrap()[0].instance_id == "inst_running"
        assert len(result_completed.unwrap()) == 1
        assert result_completed.unwrap()[0].instance_id == "inst_completed"

    def test_list_agents_empty_when_no_agents(self, tmp_state_dir: Path) -> None:
        """list_agents returns empty list when no agents exist."""
        result = list_agents()

        assert isinstance(result, Success)
        assert result.unwrap() == []

    def test_list_agents_skips_invalid_state_files(self, tmp_state_dir: Path) -> None:
        """list_agents skips directories with invalid state.json."""
        # Create valid agent
        valid = AgentStateData(
            instance_id="inst_valid",
            trace_id="tr_valid",
            persona_name="valid",
        )
        write_state("inst_valid", valid)

        # Create invalid agent directory
        invalid_dir = tmp_state_dir / "agents" / "inst_invalid"
        invalid_dir.mkdir(parents=True)
        (invalid_dir / "state.json").write_text("bad json")

        result = list_agents()

        assert isinstance(result, Success)
        agents = result.unwrap()
        assert len(agents) == 1
        assert agents[0].instance_id == "inst_valid"

    def test_list_agents_skips_directories_without_state_file(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """list_agents skips directories without state.json."""
        write_state("inst_valid", sample_state_data)

        # Create directory without state.json
        empty_dir = tmp_state_dir / "agents" / "inst_empty"
        empty_dir.mkdir(parents=True)

        result = list_agents()

        assert isinstance(result, Success)
        agents = result.unwrap()
        assert len(agents) == 1


class TestResultPersistence:
    """Tests for result.json read/write operations."""

    def test_write_result_creates_file(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """write_result creates result.json in agent directory."""
        write_state("inst_test123", sample_state_data)

        result_data = AgentResult(
            result_ref="results/output.json",
            final_state=AgentState.completed,
            token_usage=TokenUsage(prompt_tokens=100, completion_tokens=50),
        )
        result = write_result("inst_test123", result_data)

        assert isinstance(result, Success)
        result_file = tmp_state_dir / "agents" / "inst_test123" / "result.json"
        assert result_file.exists()

    def test_read_result_returns_correct_data(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """read_result returns the correct AgentResult."""
        write_state("inst_test123", sample_state_data)

        result_data = AgentResult(
            result_ref="results/output.json",
            final_state=AgentState.completed,
            token_usage=TokenUsage(prompt_tokens=100, completion_tokens=50),
        )
        write_result("inst_test123", result_data)

        result = read_result("inst_test123")

        assert isinstance(result, Success)
        data = result.unwrap()
        assert data.result_ref == "results/output.json"
        assert data.final_state == AgentState.completed
        assert data.token_usage is not None
        assert data.token_usage.total_tokens == 150

    def test_read_result_fails_for_nonexistent_agent(self, tmp_state_dir: Path) -> None:
        """read_result returns Failure for non-existent result."""
        result = read_result("inst_nonexistent")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "not found" in str(error).lower()

    def test_result_with_error(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """result.json stores error field correctly."""
        write_state("inst_test123", sample_state_data)

        result_data = AgentResult(
            final_state=AgentState.failed,
            error="Something went wrong",
        )
        write_result("inst_test123", result_data)

        result = read_result("inst_test123")
        assert isinstance(result, Success)
        data = result.unwrap()
        assert data.error == "Something went wrong"
        assert data.final_state == AgentState.failed


class TestLogging:
    """Tests for log.jsonl read/write operations."""

    def test_append_log_creates_file(self, tmp_state_dir: Path) -> None:
        """append_log creates log.jsonl if it doesn't exist."""
        result = append_log("inst_test123", "created", {"agent": "test"})

        assert isinstance(result, Success)
        log_file = tmp_state_dir / "agents" / "inst_test123" / "log.jsonl"
        assert log_file.exists()

    def test_append_log_appends_multiple_entries(self, tmp_state_dir: Path) -> None:
        """append_log appends multiple entries to log."""
        append_log("inst_test123", "created", {"step": 1})
        append_log("inst_test123", "running", {"step": 2})
        append_log("inst_test123", "completed", {"step": 3})

        log_file = tmp_state_dir / "agents" / "inst_test123" / "log.jsonl"
        lines = log_file.read_text().strip().split("\n")
        assert len(lines) == 3

        entries = [json.loads(line) for line in lines]
        assert entries[0]["event"] == "created"
        assert entries[1]["event"] == "running"
        assert entries[2]["event"] == "completed"

    def test_append_log_includes_timestamp(self, tmp_state_dir: Path) -> None:
        """append_log includes ISO timestamp in 'ts' field."""
        append_log("inst_test123", "test_event")

        log_file = tmp_state_dir / "agents" / "inst_test123" / "log.jsonl"
        entry = json.loads(log_file.read_text())

        assert "ts" in entry
        # ISO format check (basic)
        assert "T" in entry["ts"]

    def test_read_log_returns_all_entries(self, tmp_state_dir: Path) -> None:
        """read_log returns all log entries."""
        append_log("inst_test123", "event1", {"a": 1})
        append_log("inst_test123", "event2", {"b": 2})

        result = read_log("inst_test123")

        assert isinstance(result, Success)
        entries = result.unwrap()
        assert len(entries) == 2
        assert entries[0]["event"] == "event1"
        assert entries[1]["event"] == "event2"

    def test_read_log_empty_for_nonexistent_agent(self, tmp_state_dir: Path) -> None:
        """read_log returns empty list for non-existent agent."""
        result = read_log("inst_nonexistent")

        assert isinstance(result, Success)
        assert result.unwrap() == []

    def test_read_log_skips_malformed_lines(self, tmp_state_dir: Path) -> None:
        """read_log skips malformed JSON lines."""
        append_log("inst_test123", "valid_event")

        # Append malformed line directly
        log_file = tmp_state_dir / "agents" / "inst_test123" / "log.jsonl"
        with log_file.open("a") as f:
            f.write("not valid json\n")
            f.write('{"event": "also_valid"}\n')

        result = read_log("inst_test123")

        assert isinstance(result, Success)
        entries = result.unwrap()
        assert len(entries) == 2
        assert entries[0]["event"] == "valid_event"
        assert entries[1]["event"] == "also_valid"


class TestStateRecovery:
    """Tests for state recovery behaviors."""

    def test_get_state_dir_uses_env_var(
        self, tmp_state_dir: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """get_state_dir respects ANIMA_STATE environment variable."""
        result = get_state_dir()
        assert result == tmp_state_dir

    def test_get_state_dir_defaults_to_home(
        self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
    ) -> None:
        """get_state_dir defaults to ~/.anima if ANIMA_STATE not set."""
        monkeypatch.delenv("ANIMA_STATE", raising=False)
        # Temporarily change HOME for isolation
        monkeypatch.setenv("HOME", str(tmp_path))

        result = get_state_dir()

        assert result == tmp_path / ".anima"
        assert result.exists()

    def test_get_agent_dir_returns_correct_path(self, tmp_state_dir: Path) -> None:
        """get_agent_dir returns path to agent directory."""
        result = get_agent_dir("inst_test123")

        assert result == tmp_state_dir / "agents" / "inst_test123"

    def test_create_agent_state_uses_instance_id(self, tmp_state_dir: Path) -> None:
        """create_agent_state creates state with instance_id as directory key."""
        job = JobSpec(
            persona_ref="test@v1",
            instance_id="inst_test",
            max_turns=10,
            max_tokens=1000,
        )

        result = create_agent_state(
            instance_id="inst_test",
            persona_name="test-persona",
            job=job,
        )

        assert isinstance(result, Success)
        state = result.unwrap()
        assert state.instance_id == "inst_test"
        assert state.persona_name == "test-persona"
        assert state.state == AgentState.pending

        # Verify written to disk at instance_id path
        state_file = tmp_state_dir / "agents" / "inst_test" / "state.json"
        assert state_file.exists()

    def test_create_agent_state_idempotency(self, tmp_state_dir: Path) -> None:
        """create_agent_state fails if instance directory already exists."""
        job = JobSpec(
            persona_ref="test@v1",
            instance_id="inst_test",
            max_turns=10,
            max_tokens=1000,
        )

        # First creation succeeds
        result1 = create_agent_state(
            instance_id="inst_test",
            persona_name="test-persona",
            job=job,
        )
        assert isinstance(result1, Success)

        # Second creation with same instance_id fails
        result2 = create_agent_state(
            instance_id="inst_test",
            persona_name="test-persona",
            job=job,
        )
        assert isinstance(result2, Failure)
        error = result2.failure()
        assert "already exists" in str(error).lower()


class TestAtomicWriteBehavior:
    """Tests for atomic write behavior during failures."""

    def test_write_state_no_partial_on_failure(
        self,
        tmp_state_dir: Path,
        sample_state_data: AgentStateData,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """write_state cleans up temp file on failure and doesn't leave partial state."""
        # Make write_text fail after tmp creation
        original_write_text = Path.write_text
        call_count = [0]

        def failing_write_text(self: Path, content: str, **kwargs) -> int:
            call_count[0] += 1
            if ".tmp" in str(self):
                raise OSError("Simulated write failure")
            return original_write_text(self, content, **kwargs)

        monkeypatch.setattr(Path, "write_text", failing_write_text)

        result = write_state("inst_test123", sample_state_data)

        assert isinstance(result, Failure)
        # State file should not exist
        state_file = tmp_state_dir / "agents" / "inst_test123" / "state.json"
        assert not state_file.exists()
        # Temp file should be cleaned up
        tmp_files = (
            list((tmp_state_dir / "agents" / "inst_test123").glob("*.tmp"))
            if (tmp_state_dir / "agents" / "inst_test123").exists()
            else []
        )
        assert len(tmp_files) == 0

    def test_write_result_atomic(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """write_result uses atomic write pattern."""
        write_state("inst_test123", sample_state_data)

        result_data = AgentResult(final_state=AgentState.completed)
        write_result("inst_test123", result_data)

        result_file = tmp_state_dir / "agents" / "inst_test123" / "result.json"
        assert result_file.exists()
        # Can read back correctly
        data = json.loads(result_file.read_text())
        assert data["final_state"] == "completed"


class TestProcessLiveness:
    """Tests for process liveness checking and stale agent reaping."""

    def test_is_alive_returns_false_for_nonexistent_agent(
        self, tmp_state_dir: Path
    ) -> None:
        """is_agent_process_alive returns Failure for non-existent agent."""
        result = is_agent_process_alive("inst_nonexistent")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "not found" in str(error).lower()

    def test_is_alive_returns_false_for_pending_state(
        self, tmp_state_dir: Path
    ) -> None:
        """is_agent_process_alive returns False for pending (non-running) state."""
        job = JobSpec(
            persona_ref="test@v1",
            instance_id="inst_test",
            max_turns=10,
            max_tokens=1000,
        )
        create_agent_state(
            instance_id="inst_test",
            persona_name="test-persona",
            job=job,
        )

        result = is_agent_process_alive("inst_test")

        assert isinstance(result, Success)
        assert result.unwrap() is False

    def test_is_alive_returns_false_for_running_state_without_pid(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """is_agent_process_alive returns False for running agent with no PID."""
        sample_state_data.state = AgentState.running
        sample_state_data.pid = None
        write_state("inst_test123", sample_state_data)

        result = is_agent_process_alive("inst_test123")

        assert isinstance(result, Success)
        assert result.unwrap() is False

    def test_is_alive_returns_false_for_dead_pid(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """is_agent_process_alive returns False for dead PID.

        Uses a PID that is guaranteed not to exist (99999999).
        """
        sample_state_data.state = AgentState.running
        sample_state_data.pid = 99999999  # Non-existent PID
        write_state("inst_test123", sample_state_data)

        result = is_agent_process_alive("inst_test123")

        assert isinstance(result, Success)
        assert result.unwrap() is False

    def test_reap_stale_agent_returns_failure_for_nonexistent_agent(
        self, tmp_state_dir: Path
    ) -> None:
        """reap_stale_agent returns Failure for non-existent agent."""
        result = reap_stale_agent("inst_nonexistent")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "not found" in str(error).lower()

    def test_reap_stale_agent_returns_failure_for_non_running_state(
        self, tmp_state_dir: Path
    ) -> None:
        """reap_stale_agent returns Failure for agent not in running state."""
        job = JobSpec(
            persona_ref="test@v1",
            instance_id="inst_test",
            max_turns=10,
            max_tokens=1000,
        )
        create_agent_state(
            instance_id="inst_test",
            persona_name="test-persona",
            job=job,
        )

        result = reap_stale_agent("inst_test")

        assert isinstance(result, Failure)
        error = result.failure()
        assert "must be running" in str(error).lower()

    def test_reap_stale_agent_transitions_to_failed(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """reap_stale_agent transitions running -> failed with error."""
        sample_state_data.state = AgentState.running
        sample_state_data.pid = 99999999  # Dead PID
        write_state("inst_test123", sample_state_data)

        result = reap_stale_agent("inst_test123")

        assert isinstance(result, Success)
        state_data = result.unwrap()
        assert state_data.state == AgentState.failed
        assert state_data.error is not None
        assert "stale" in state_data.error.lower()

    def test_reap_stale_agent_logs_event(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """reap_stale_agent logs agent_reaped event."""
        sample_state_data.state = AgentState.running
        sample_state_data.pid = 12345
        write_state("inst_test123", sample_state_data)

        reap_stale_agent("inst_test123")

        log_result = read_log("inst_test123")
        assert isinstance(log_result, Success)
        entries = log_result.unwrap()

        # Find the agent_reaped entry
        reaped_entries = [e for e in entries if e.get("event") == "agent_reaped"]
        assert len(reaped_entries) == 1
        entry = reaped_entries[0]
        assert entry["details"]["reason"] == "stale_state"
        assert entry["details"]["pid"] == 12345

    def test_is_alive_returns_true_for_running_state_with_alive_pid(
        self, tmp_state_dir: Path, sample_state_data: AgentStateData
    ) -> None:
        """is_agent_process_alive returns True for running agent with alive PID."""
        import os

        sample_state_data.state = AgentState.running
        sample_state_data.pid = os.getpid()  # Current process is alive
        write_state("inst_test123", sample_state_data)

        result = is_agent_process_alive("inst_test123")

        assert isinstance(result, Success)
        assert result.unwrap() is True


class TestLogTailer:
    """Tests for LogTailer class (logs-follow-seek.implement-seek-reader)."""

    def test_reads_new_entries(self, tmp_path: Path) -> None:
        """LogTailer reads new entries appended to log file."""
        log_file = tmp_path / "test.log"

        # Create tailer and write initial content
        tailer = LogTailer(log_file)

        # Write some log entries
        with log_file.open("a") as f:
            f.write('{"event": "start"}\n')
            f.write('{"event": "run"}\n')

        # Read new entries
        entries = tailer.read_new()

        assert len(entries) == 2
        assert entries[0]["event"] == "start"
        assert entries[1]["event"] == "run"

    def test_reads_multiple_appends(self, tmp_path: Path) -> None:
        """LogTailer reads entries from multiple appends correctly."""
        log_file = tmp_path / "test.log"

        tailer = LogTailer(log_file)

        # First append
        with log_file.open("a") as f:
            f.write('{"event": "first"}\n')

        entries1 = tailer.read_new()
        assert len(entries1) == 1
        assert entries1[0]["event"] == "first"

        # Second append
        with log_file.open("a") as f:
            f.write('{"event": "second"}\n')
            f.write('{"event": "third"}\n')

        entries2 = tailer.read_new()
        assert len(entries2) == 2
        assert entries2[0]["event"] == "second"
        assert entries2[1]["event"] == "third"

    def test_partial_line_buffered_until_newline(self, tmp_path: Path) -> None:
        """LogTailer buffers partial lines until newline arrives."""
        log_file = tmp_path / "test.log"

        tailer = LogTailer(log_file)

        # Write partial line (no newline)
        with log_file.open("a") as f:
            f.write('{"event": "partial"')

        # Should return empty - line is incomplete
        entries = tailer.read_new()
        assert entries == []

        # Complete the line
        with log_file.open("a") as f:
            f.write("}\n")

        # Now should get the complete entry
        entries = tailer.read_new()
        assert len(entries) == 1
        assert entries[0]["event"] == "partial"

    def test_truncation_resets(self, tmp_path: Path) -> None:
        """LogTailer detects truncation and resets position."""
        log_file = tmp_path / "test.log"

        tailer = LogTailer(log_file)

        # Write initial content
        with log_file.open("a") as f:
            f.write('{"event": "old1"}\n')
            f.write('{"event": "old2"}\n')

        # Read initial entries
        entries = tailer.read_new()
        assert len(entries) == 2

        # Truncate file
        log_file.write_text("")

        # Write new content after truncation
        with log_file.open("a") as f:
            f.write('{"event": "new1"}\n')

        # Should detect truncation and read from beginning
        entries = tailer.read_new()
        assert len(entries) == 1
        assert entries[0]["event"] == "new1"

    def test_recreate_resets_when_replacement_is_not_smaller(
        self, tmp_path: Path
    ) -> None:
        """LogTailer resets on file replacement even when new size >= old position."""
        log_file = tmp_path / "test.log"
        tailer = LogTailer(log_file)

        with log_file.open("a") as f:
            f.write('{"event": "old"}\n')

        initial_entries = tailer.read_new()
        assert len(initial_entries) == 1
        assert initial_entries[0]["event"] == "old"

        replacement_file = tmp_path / "replacement.log"
        replacement_file.write_text(
            '{"event": "after_truncate", "padding": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}\n'
        )
        replacement_file.replace(log_file)

        entries = tailer.read_new()
        assert len(entries) == 1
        assert entries[0]["event"] == "after_truncate"

    def test_nonexistent_file_appears_later(self, tmp_path: Path) -> None:
        """LogTailer handles nonexistent file that appears later."""
        log_file = tmp_path / "nonexistent.log"

        # Tailer on nonexistent file
        tailer = LogTailer(log_file)

        # File doesn't exist yet
        entries = tailer.read_new()
        assert entries == []

        # Create file and write content
        log_file.write_text('{"event": "appeared"}\n')

        # Should read from beginning
        entries = tailer.read_new()
        assert len(entries) == 1
        assert entries[0]["event"] == "appeared"

    def test_empty_read_returns_empty_list(self, tmp_path: Path) -> None:
        """LogTailer returns empty list when no new content."""
        log_file = tmp_path / "test.log"

        # Create file with initial content
        log_file.write_text('{"event": "start"}\n')

        # Create tailer - it starts at end of file
        tailer = LogTailer(log_file)

        # Read with no new content - should be empty
        entries = tailer.read_new()
        assert entries == []

        # Write more content
        with log_file.open("a") as f:
            f.write('{"event": "new"}\n')

        # Now should get the new entry
        entries = tailer.read_new()
        assert len(entries) == 1
        assert entries[0]["event"] == "new"

        # Read again with no new content
        entries = tailer.read_new()
        assert entries == []

    def test_tolerate_nonexistent_file_at_init(self, tmp_path: Path) -> None:
        """LogTailer tolerates nonexistent file at initialization."""
        log_file = tmp_path / "missing.log"

        # Should not raise - just initializes
        tailer = LogTailer(log_file)

        # File still doesn't exist
        assert not log_file.exists()

        # Should work fine
        entries = tailer.read_new()
        assert entries == []
