"""Tests for `skilark link` command and the link-related SkilarkClient methods."""

import json
from unittest.mock import MagicMock, patch

import httpx
import pytest

from skilark_cli.client import SkilarkClient
from skilark_cli.commands.link import run as link_run


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

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


# ---------------------------------------------------------------------------
# SkilarkClient.create_link_code
# ---------------------------------------------------------------------------

class TestCreateLinkCode:
    def test_posts_to_correct_path(self):
        def handler(request):
            assert request.url.path == "/v1/users/link-code"
            assert request.method == "POST"
            return httpx.Response(201, json={"code": "ABC123", "expires_at": "2026-02-27T12:10:00Z"})

        client = _make_client(handler)
        client.create_link_code()

    def test_sends_user_header(self):
        def handler(request):
            assert request.headers["X-Skilark-User"] == "uuid-1"
            return httpx.Response(201, json={"code": "ABC123", "expires_at": "2026-02-27T12:10:00Z"})

        client = _make_client(handler, user_id="uuid-1")
        client.create_link_code()

    def test_returns_parsed_json(self):
        def handler(request):
            return httpx.Response(201, json={"code": "XYZ789", "expires_at": "2026-02-27T12:10:00Z"})

        client = _make_client(handler)
        result = client.create_link_code()
        assert result["code"] == "XYZ789"

    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.create_link_code()


# ---------------------------------------------------------------------------
# SkilarkClient.redeem_link_code
# ---------------------------------------------------------------------------

class TestRedeemLinkCode:
    def test_posts_to_correct_path(self):
        def handler(request):
            assert request.url.path == "/v1/users/link"
            assert request.method == "POST"
            return httpx.Response(200, json={"id": "uuid-1"})

        client = _make_client(handler)
        client.redeem_link_code(code="ABC123", channel_type="cli", channel_id="uuid-1")

    def test_sends_correct_body(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["code"] == "ABC123"
            assert body["channel_type"] == "cli"
            assert body["channel_id"] == "uuid-1"
            return httpx.Response(200, json={"id": "uuid-1"})

        client = _make_client(handler)
        client.redeem_link_code(code="ABC123", channel_type="cli", channel_id="uuid-1")

    def test_returns_parsed_json(self):
        def handler(request):
            return httpx.Response(200, json={"id": "uuid-99", "topics": ["python"]})

        client = _make_client(handler)
        user = client.redeem_link_code(code="ABC123", channel_type="cli", channel_id="uuid-1")
        assert user["id"] == "uuid-99"

    def test_raises_on_expired_code(self):
        def handler(request):
            return httpx.Response(410, json={"error": "code expired"})

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.redeem_link_code(code="OLDONE", channel_type="cli", channel_id="uuid-1")

    def test_raises_on_invalid_code(self):
        def handler(request):
            return httpx.Response(404, json={"error": "code not found"})

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.redeem_link_code(code="NOTFND", channel_type="cli", channel_id="uuid-1")


# ---------------------------------------------------------------------------
# link command — generate flow
# ---------------------------------------------------------------------------

class TestLinkCommandGenerate:
    def test_calls_create_link_code_when_no_code(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client.create_link_code.return_value = {
            "code": "ABC123",
            "expires_at": "2026-02-27T12:10:00Z",
        }

        with patch("skilark_cli.commands.link.console"):
            link_run(client=mock_client, code=None)

        mock_client.create_link_code.assert_called_once()
        mock_client.redeem_link_code.assert_not_called()

    def test_generate_displays_code(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client.create_link_code.return_value = {"code": "XYZ789"}

        mock_console = MagicMock()
        with patch("skilark_cli.commands.link.console", mock_console):
            link_run(client=mock_client, code=None)

        all_calls = " ".join(str(call) for call in mock_console.print.call_args_list)
        assert "XYZ789" in all_calls

    def test_generate_mentions_expiry(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client.create_link_code.return_value = {"code": "ABC123"}

        mock_console = MagicMock()
        with patch("skilark_cli.commands.link.console", mock_console):
            link_run(client=mock_client, code=None)

        all_calls = " ".join(str(call) for call in mock_console.print.call_args_list)
        assert "10 minutes" in all_calls


# ---------------------------------------------------------------------------
# link command — redeem flow
# ---------------------------------------------------------------------------

class TestLinkCommandRedeem:
    def test_calls_redeem_when_code_given(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client._user_id = "uuid-1"
        mock_client.redeem_link_code.return_value = {"id": "uuid-1"}

        with patch("skilark_cli.commands.link.console"):
            link_run(client=mock_client, code="ABC123")

        mock_client.redeem_link_code.assert_called_once_with(
            code="ABC123",
            channel_type="cli",
            channel_id="uuid-1",
        )
        mock_client.create_link_code.assert_not_called()

    def test_uppercases_code_before_sending(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client._user_id = "uuid-1"
        mock_client.redeem_link_code.return_value = {"id": "uuid-1"}

        with patch("skilark_cli.commands.link.console"):
            link_run(client=mock_client, code="abc123")

        _, kwargs = mock_client.redeem_link_code.call_args
        assert kwargs["code"] == "ABC123"

    def test_strips_whitespace_from_code(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client._user_id = "uuid-1"
        mock_client.redeem_link_code.return_value = {"id": "uuid-1"}

        with patch("skilark_cli.commands.link.console"):
            link_run(client=mock_client, code="  ABC123  ")

        _, kwargs = mock_client.redeem_link_code.call_args
        assert kwargs["code"] == "ABC123"

    def test_redeem_displays_success_message(self):
        mock_client = MagicMock(spec=SkilarkClient)
        mock_client._user_id = "uuid-1"
        mock_client.redeem_link_code.return_value = {"id": "uuid-1"}

        mock_console = MagicMock()
        with patch("skilark_cli.commands.link.console", mock_console):
            link_run(client=mock_client, code="ABC123")

        all_calls = " ".join(str(call) for call in mock_console.print.call_args_list)
        assert "linked" in all_calls.lower()


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

class TestLinkDispatch:
    def test_first_run_exits_with_setup_hint(self):
        """dispatch() exits cleanly with a setup hint when no config exists."""
        from skilark_cli.commands.link import dispatch

        mock_store = MagicMock()
        mock_store.is_first_run.return_value = True

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

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

    def test_dispatch_no_code_calls_run_with_none(self):
        """dispatch([]) resolves config and calls run(client, code=None)."""
        from skilark_cli.commands.link import dispatch

        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(spec=SkilarkClient)
        mock_client.create_link_code.return_value = {"code": "ABC123"}

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

        mock_run.assert_called_once_with(client=mock_client, code=None)

    def test_dispatch_with_code_calls_run_with_code(self):
        """dispatch(['XYZ789']) resolves config and calls run(client, code='XYZ789')."""
        from skilark_cli.commands.link import dispatch

        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(spec=SkilarkClient)

        with (
            patch("skilark_cli.commands.link.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.link.SkilarkClient", return_value=mock_client),
            patch("skilark_cli.commands.link.run") as mock_run,
            patch("skilark_cli.commands.link.console"),
        ):
            dispatch(["XYZ789"])

        mock_run.assert_called_once_with(client=mock_client, code="XYZ789")


# ---------------------------------------------------------------------------
# main.py dispatch
# ---------------------------------------------------------------------------

class TestMainLinkDispatch:
    def test_link_routes_to_dispatch(self):
        """main() routes `link` to link.dispatch() without error."""
        import sys
        from skilark_cli.main import main

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

        mock_dispatch.assert_called_once_with([])

    def test_link_with_code_routes_to_dispatch(self):
        """main() routes `link ABC123` to link.dispatch(['ABC123'])."""
        import sys
        from skilark_cli.main import main

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

        mock_dispatch.assert_called_once_with(["ABC123"])
