"""
Tests for the `skilark signals` command and SkilarkClient.get_signals().

All command tests call ``run_signals()`` directly with a mock client so
there is no network I/O or filesystem access.  Client tests use
httpx.MockTransport to intercept HTTP calls at the transport layer.
"""

import httpx
import pytest
from unittest.mock import MagicMock, patch

from skilark_cli.client import SkilarkClient
from skilark_cli.commands.signals import run_signals, dispatch


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


def _make_client(handler, user_id: str | None = None) -> SkilarkClient:
    """Return a SkilarkClient backed by a MockTransport."""
    return SkilarkClient(
        base_url="https://test",
        user_id=user_id,
        transport=httpx.MockTransport(handler),
    )


def _make_signal(
    id: str = "sig-1",
    type: str = "company_surge",
    title: str = "Anthropic posted 12 roles this week — 3x their average",
    body: str = "ML Infrastructure and Backend Engineer lead the surge.",
    metadata: dict | None = None,
) -> dict:
    """Return a minimal but valid signal dict."""
    return {
        "id": id,
        "type": type,
        "title": title,
        "body": body,
        "metadata": metadata or {"company": "anthropic", "this_week": 12},
    }


def _make_response(
    week_of: str = "2026-02-23",
    signals: list | None = None,
) -> dict:
    """Return a minimal but valid signals API response dict."""
    return {
        "week_of": week_of,
        "signals": signals if signals is not None else [_make_signal()],
    }


# ---------------------------------------------------------------------------
# SkilarkClient.get_signals — HTTP transport tests
# ---------------------------------------------------------------------------


class TestGetSignals:
    def test_hits_correct_path(self):
        def handler(request):
            assert request.url.path == "/v1/signals"
            return httpx.Response(200, json=_make_response())

        client = _make_client(handler)
        client.get_signals()

    def test_sends_limit_zero_for_all(self):
        def handler(request):
            assert "limit=0" in str(request.url)
            return httpx.Response(200, json=_make_response())

        client = _make_client(handler)
        client.get_signals(limit=0)

    def test_sends_limit_param_when_given(self):
        def handler(request):
            assert "limit=5" in str(request.url)
            return httpx.Response(200, json=_make_response())

        client = _make_client(handler)
        client.get_signals(limit=5)

    def test_returns_parsed_json(self):
        def handler(request):
            return httpx.Response(200, json=_make_response(week_of="2026-02-23"))

        client = _make_client(handler)
        result = client.get_signals()
        assert result["week_of"] == "2026-02-23"
        assert isinstance(result["signals"], list)

    def test_raises_on_server_error(self):
        def handler(request):
            return httpx.Response(500, json={"error": "internal"})

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.get_signals()


# ---------------------------------------------------------------------------
# run_signals — rendering tests
# ---------------------------------------------------------------------------


class TestRunSignals:
    def test_displays_signal_title(self):
        """Signal title appears in console output."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response()

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=5, show_all=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "Anthropic posted 12 roles this week" in all_output

    def test_displays_signal_body(self):
        """Signal body appears (possibly dim) in console output."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response()

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=5, show_all=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "ML Infrastructure" in all_output

    def test_displays_week_of_label(self):
        """Week-of date is shown in the header."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response(week_of="2026-02-23")

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=5, show_all=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "Feb 23" in all_output

    def test_shows_all_hint_when_not_show_all(self):
        """The --all hint is shown when show_all=False."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response()

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=5, show_all=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "--all" in all_output

    def test_no_hint_when_show_all(self):
        """The --all hint is suppressed when show_all=True."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response()

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=0, show_all=True)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "--all" not in all_output

    def test_empty_signals_shows_no_signals_message(self):
        """When signals list is empty a user-friendly message is printed."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response(signals=[])

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=5, show_all=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "no signals" in all_output.lower()

    def test_multiple_signals_are_numbered(self):
        """Multiple signals are rendered as a numbered list."""
        signals = [
            _make_signal(id="s1", title="Signal one"),
            _make_signal(id="s2", title="Signal two"),
        ]
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response(signals=signals)

        mock_console = MagicMock()
        with patch("skilark_cli.commands.signals.console", mock_console):
            run_signals(mock_client, limit=5, show_all=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "1." in all_output
        assert "2." in all_output
        assert "Signal one" in all_output
        assert "Signal two" in all_output

    def test_passes_limit_to_client(self):
        """run_signals passes the limit argument straight to get_signals."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response()

        with patch("skilark_cli.commands.signals.console"):
            run_signals(mock_client, limit=3, show_all=False)

        mock_client.get_signals.assert_called_once_with(limit=3)

    def test_passes_zero_limit_when_show_all(self):
        """run_signals passes limit=0 when show_all is True."""
        mock_client = MagicMock()
        mock_client.get_signals.return_value = _make_response()

        with patch("skilark_cli.commands.signals.console"):
            run_signals(mock_client, limit=0, show_all=True)

        mock_client.get_signals.assert_called_once_with(limit=0)


# ---------------------------------------------------------------------------
# dispatch() — config/first-run gate
# ---------------------------------------------------------------------------


class TestSignalsDispatch:
    def test_first_run_exits_with_setup_hint(self):
        """dispatch() exits cleanly with a setup hint when no config exists."""
        mock_store = MagicMock()
        mock_store.is_first_run.return_value = True

        mock_console = MagicMock()
        with (
            patch("skilark_cli.commands.signals.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.signals.console", mock_console),
        ):
            dispatch([])

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "skilark today" in all_output.lower()

    def test_dispatch_no_flags_uses_default_limit(self):
        """dispatch([]) calls run_signals with limit=5 and show_all=False."""
        mock_config = {"user_id": "uuid-1", "topics": ["python"], "api_url": "https://test"}
        mock_store = MagicMock()
        mock_store.is_first_run.return_value = False
        mock_store.load.return_value = mock_config
        mock_client = MagicMock()

        with (
            patch("skilark_cli.commands.signals.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.signals.SkilarkClient", return_value=mock_client),
            patch("skilark_cli.commands.signals.run_signals") as mock_run,
        ):
            dispatch([])

        mock_run.assert_called_once_with(mock_client, limit=5, show_all=False)

    def test_dispatch_all_flag_passes_zero_limit(self):
        """dispatch(['--all']) calls run_signals with limit=0 and show_all=True."""
        mock_config = {"user_id": "uuid-1", "topics": ["python"], "api_url": "https://test"}
        mock_store = MagicMock()
        mock_store.is_first_run.return_value = False
        mock_store.load.return_value = mock_config
        mock_client = MagicMock()

        with (
            patch("skilark_cli.commands.signals.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.signals.SkilarkClient", return_value=mock_client),
            patch("skilark_cli.commands.signals.run_signals") as mock_run,
        ):
            dispatch(["--all"])

        mock_run.assert_called_once_with(mock_client, limit=0, show_all=True)

    def test_dispatch_constructs_client_from_config(self):
        """dispatch() builds SkilarkClient using api_url and user_id from config."""
        mock_config = {"user_id": "uuid-42", "topics": ["python"], "api_url": "https://api.example.com"}
        mock_store = MagicMock()
        mock_store.is_first_run.return_value = False
        mock_store.load.return_value = mock_config

        with (
            patch("skilark_cli.commands.signals.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.signals.SkilarkClient") as mock_client_cls,
            patch("skilark_cli.commands.signals.run_signals"),
        ):
            dispatch([])

        mock_client_cls.assert_called_once_with(
            base_url="https://api.example.com", user_id="uuid-42"
        )


# ---------------------------------------------------------------------------
# main.py routing
# ---------------------------------------------------------------------------


class TestMainSignalsRouting:
    def test_signals_routes_to_dispatch(self):
        """main() routes `signals` to signals.dispatch() with no extra args."""
        import sys
        from skilark_cli.main import main

        with patch("skilark_cli.commands.signals.dispatch") as mock_dispatch:
            with patch.object(sys, "argv", ["skilark", "signals"]):
                main()

        mock_dispatch.assert_called_once_with([])

    def test_signals_all_routes_to_dispatch_with_flag(self):
        """main() routes `signals --all` to signals.dispatch(['--all'])."""
        import sys
        from skilark_cli.main import main

        with patch("skilark_cli.commands.signals.dispatch") as mock_dispatch:
            with patch.object(sys, "argv", ["skilark", "signals", "--all"]):
                main()

        mock_dispatch.assert_called_once_with(["--all"])
