"""Tests for the ``loom logs`` CLI command.

These tests use Click's CliRunner with isolated filesystem — no Docker,
Postgres, or Redis required.
"""

from __future__ import annotations

import json
from pathlib import Path

import pytest
from click.testing import CliRunner

from loom.cli import cli


@pytest.fixture
def runner() -> CliRunner:
    return CliRunner()


def _make_log_entry(
    message: str,
    level: str = "INFO",
    agent: str | None = None,
    source: str | None = None,
    logger_name: str = "loom",
) -> str:
    """Build a JSON log line matching the structured logging format."""
    entry: dict = {
        "timestamp": "2026-01-15T10:30:00.000",
        "severity": level,
        "message": message,
        "logger": logger_name,
    }
    if agent is not None:
        entry["agent"] = agent
    if source is not None:
        entry["source"] = source
    return json.dumps(entry)


class TestLogsCommand:
    """Tests for the ``loom logs`` command."""

    def test_no_logs_directory(self, runner: CliRunner) -> None:
        """Logs command when .loom/logs/ doesn't exist exits gracefully."""
        with runner.isolated_filesystem():
            result = runner.invoke(cli, ["logs"])

        assert result.exit_code == 0
        assert "no logs directory" in result.output.lower()

    def test_empty_logs_directory(self, runner: CliRunner) -> None:
        """Logs command when .loom/logs/ exists but is empty."""
        with runner.isolated_filesystem():
            Path(".loom/logs").mkdir(parents=True)
            result = runner.invoke(cli, ["logs"])

        assert result.exit_code == 0
        assert "no log files" in result.output.lower()

    def test_reads_log_files(self, runner: CliRunner) -> None:
        """Log files in .loom/logs/ are read and displayed."""
        with runner.isolated_filesystem():
            log_dir = Path(".loom/logs")
            log_dir.mkdir(parents=True)

            lines = [
                _make_log_entry("Server started", level="INFO"),
                _make_log_entry("Processing task loom-abc12345", level="INFO"),
                _make_log_entry("Task completed", level="INFO"),
            ]
            (log_dir / "app.log").write_text("\n".join(lines) + "\n")

            result = runner.invoke(cli, ["logs"])

        assert result.exit_code == 0
        assert "Server started" in result.output
        assert "Processing task loom-abc12345" in result.output
        assert "Task completed" in result.output

    def test_n_lines_flag(self, runner: CliRunner) -> None:
        """The -n/--lines flag limits the number of entries shown."""
        with runner.isolated_filesystem():
            log_dir = Path(".loom/logs")
            log_dir.mkdir(parents=True)

            lines = [
                _make_log_entry(f"Log entry {i}", level="INFO")
                for i in range(20)
            ]
            (log_dir / "app.log").write_text("\n".join(lines) + "\n")

            # Ask for only 5 lines
            result = runner.invoke(cli, ["logs", "-n", "5"])

        assert result.exit_code == 0
        # Should contain the last 5 entries (15..19)
        assert "Log entry 19" in result.output
        assert "Log entry 15" in result.output
        # Should NOT contain early entries
        assert "Log entry 0" not in result.output

    def test_agent_filter(self, runner: CliRunner) -> None:
        """The agent argument filters log entries by agent name."""
        with runner.isolated_filesystem():
            log_dir = Path(".loom/logs")
            log_dir.mkdir(parents=True)

            lines = [
                _make_log_entry("Task A started", agent="agent-alpha"),
                _make_log_entry("Task B started", agent="agent-beta"),
                _make_log_entry("Task A finished", agent="agent-alpha"),
                _make_log_entry("Task C started", source="agent-gamma"),
            ]
            (log_dir / "app.log").write_text("\n".join(lines) + "\n")

            result = runner.invoke(cli, ["logs", "agent-alpha"])

        assert result.exit_code == 0
        assert "Task A started" in result.output
        assert "Task A finished" in result.output
        # Entries from other agents should be filtered out
        assert "Task B started" not in result.output
        assert "Task C started" not in result.output

    def test_agent_filter_matches_source_field(self, runner: CliRunner) -> None:
        """The agent filter also matches the 'source' JSON field."""
        with runner.isolated_filesystem():
            log_dir = Path(".loom/logs")
            log_dir.mkdir(parents=True)

            lines = [
                _make_log_entry("Task from source", source="my-agent"),
                _make_log_entry("Unrelated entry", agent="other-agent"),
            ]
            (log_dir / "app.log").write_text("\n".join(lines) + "\n")

            result = runner.invoke(cli, ["logs", "my-agent"])

        assert result.exit_code == 0
        assert "Task from source" in result.output
        assert "Unrelated entry" not in result.output

    def test_json_parse_error_resilience(self, runner: CliRunner) -> None:
        """Malformed JSON in a log file doesn't crash the command."""
        with runner.isolated_filesystem():
            log_dir = Path(".loom/logs")
            log_dir.mkdir(parents=True)

            content = (
                _make_log_entry("Valid entry 1") + "\n"
                "this is not valid json {{{{\n"
                + _make_log_entry("Valid entry 2") + "\n"
            )
            (log_dir / "app.log").write_text(content)

            result = runner.invoke(cli, ["logs"])

        assert result.exit_code == 0
        assert "Valid entry 1" in result.output
        assert "Valid entry 2" in result.output
        # The malformed line should still appear (as raw text)
        assert "this is not valid json" in result.output

    def test_agent_filter_no_match(self, runner: CliRunner) -> None:
        """When agent filter matches nothing, a warning is printed."""
        with runner.isolated_filesystem():
            log_dir = Path(".loom/logs")
            log_dir.mkdir(parents=True)

            lines = [
                _make_log_entry("Some entry", agent="other-agent"),
            ]
            (log_dir / "app.log").write_text("\n".join(lines) + "\n")

            result = runner.invoke(cli, ["logs", "nonexistent-agent"])

        assert result.exit_code == 0
        assert "no log entries matching" in result.output.lower()
