"""Unit tests for SkilarkClient.

All HTTP calls are intercepted with httpx.MockTransport so no real network
traffic is produced.  Each test wires up exactly the response it expects,
keeping tests self-contained and deterministic.
"""

import json

import httpx
import pytest

from skilark_cli.client import SkilarkClient


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

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),
    )


# ---------------------------------------------------------------------------
# create_user
# ---------------------------------------------------------------------------

class TestCreateUser:
    def test_posts_to_correct_path(self):
        def handler(request):
            assert request.url.path == "/v1/users"
            return httpx.Response(201, json={"id": "uuid-1", "topics": ["python"]})

        client = _make_client(handler)
        client.create_user(topics=["python"])

    def test_sends_topics_in_body(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["topics"] == ["python"]
            return httpx.Response(201, json={"id": "uuid-1", "topics": ["python"]})

        client = _make_client(handler)
        client.create_user(topics=["python"])

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

        client = _make_client(handler)
        user = client.create_user(topics=["python"])
        assert user["id"] == "uuid-1"

    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_user(topics=["python"])


# ---------------------------------------------------------------------------
# get_next_challenge
# ---------------------------------------------------------------------------

class TestGetNextChallenge:
    def test_returns_challenge_on_200(self):
        def handler(request):
            return httpx.Response(200, json={"id": "ch1", "question": "What prints?"})

        client = _make_client(handler, user_id="uuid-1")
        ch = client.get_next_challenge(topics=["python"])
        assert ch["id"] == "ch1"

    def test_returns_none_on_404(self):
        def handler(request):
            return httpx.Response(404, json={"error": "no challenges"})

        client = _make_client(handler, user_id="uuid-1")
        result = client.get_next_challenge(topics=["python"])
        assert result is None

    def test_sends_user_header(self):
        def handler(request):
            assert request.headers["X-Skilark-User"] == "uuid-1"
            return httpx.Response(200, json={"id": "ch1", "question": "Q"})

        client = _make_client(handler, user_id="uuid-1")
        client.get_next_challenge(topics=["python"])

    def test_includes_topics_in_query_string(self):
        def handler(request):
            assert "python" in str(request.url)
            return httpx.Response(200, json={"id": "ch1", "question": "Q"})

        client = _make_client(handler, user_id="uuid-1")
        client.get_next_challenge(topics=["python"])

    def test_no_user_header_when_user_id_absent(self):
        def handler(request):
            assert "X-Skilark-User" not in request.headers
            return httpx.Response(200, json={"id": "ch1", "question": "Q"})

        client = _make_client(handler)
        client.get_next_challenge(topics=["python"])

    def test_raises_on_non_404_error(self):
        def handler(request):
            return httpx.Response(503, json={"error": "unavailable"})

        client = _make_client(handler, user_id="uuid-1")
        with pytest.raises(httpx.HTTPStatusError):
            client.get_next_challenge(topics=["python"])


# ---------------------------------------------------------------------------
# complete_challenge
# ---------------------------------------------------------------------------

class TestCompleteChallenge:
    def test_posts_to_correct_path(self):
        def handler(request):
            assert request.url.path == "/v1/challenges/ch1/complete"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler, user_id="uuid-1")
        client.complete_challenge("ch1", answer="1 2", correct=True, self_corrected=False)

    def test_sends_correct_body(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["correct"] is True
            assert body["self_corrected"] is False
            assert body["answer"] == "1 2"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler, user_id="uuid-1")
        client.complete_challenge("ch1", answer="1 2", correct=True, self_corrected=False)

    def test_sends_user_header(self):
        def handler(request):
            assert request.headers["X-Skilark-User"] == "uuid-1"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler, user_id="uuid-1")
        client.complete_challenge("ch1", answer="x", correct=False, self_corrected=True)

    def test_raises_on_error_response(self):
        def handler(request):
            return httpx.Response(400, json={"error": "bad request"})

        client = _make_client(handler, user_id="uuid-1")
        with pytest.raises(httpx.HTTPStatusError):
            client.complete_challenge("ch1", answer="x", correct=True, self_corrected=False)


# ---------------------------------------------------------------------------
# get_stats
# ---------------------------------------------------------------------------

class TestGetStats:
    def test_hits_correct_path(self):
        def handler(request):
            assert request.url.path == "/v1/users/me/stats"
            return httpx.Response(200, json={"streak": 7, "total_completed": 47})

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

    def test_returns_parsed_stats(self):
        def handler(request):
            return httpx.Response(200, json={"streak": 7, "total_completed": 47})

        client = _make_client(handler, user_id="uuid-1")
        stats = client.get_stats()
        assert stats["streak"] == 7
        assert stats["total_completed"] == 47

    def test_sends_user_header(self):
        def handler(request):
            assert request.headers["X-Skilark-User"] == "uuid-1"
            return httpx.Response(200, json={"streak": 0, "total_completed": 0})

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

    def test_raises_on_error_response(self):
        def handler(request):
            return httpx.Response(401, json={"error": "unauthorized"})

        client = _make_client(handler, user_id="uuid-1")
        with pytest.raises(httpx.HTTPStatusError):
            client.get_stats()


# ---------------------------------------------------------------------------
# get_next_coding_problem
# ---------------------------------------------------------------------------

class TestGetNextCodingProblem:
    def test_hits_correct_path_with_params(self):
        def handler(request):
            assert request.url.path == "/v1/coding-problems/next"
            assert "topics=dsa" in str(request.url)
            assert "language=python" in str(request.url)
            return httpx.Response(200, json={"id": "cp1", "slug": "two-sum"})

        client = _make_client(handler, user_id="uuid-1")
        result = client.get_next_coding_problem(topics=["dsa"], language="python")
        assert result["id"] == "cp1"

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

        client = _make_client(handler, user_id="uuid-1")
        result = client.get_next_coding_problem(topics=["dsa"], language="python")
        assert result is None

    def test_multiple_topics_joined_with_comma(self):
        def handler(request):
            assert "topics=dsa%2Cpython" in str(request.url) or "topics=dsa,python" in str(request.url)
            return httpx.Response(200, json={"id": "cp2", "slug": "binary-search"})

        client = _make_client(handler, user_id="uuid-1")
        client.get_next_coding_problem(topics=["dsa", "python"], language="java")

    def test_sends_user_header(self):
        def handler(request):
            assert request.headers.get("X-Skilark-User") == "uuid-1"
            return httpx.Response(200, json={"id": "cp1", "slug": "two-sum"})

        client = _make_client(handler, user_id="uuid-1")
        client.get_next_coding_problem(topics=["dsa"], language="python")

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

        client = _make_client(handler, user_id="uuid-1")
        with pytest.raises(httpx.HTTPStatusError):
            client.get_next_coding_problem(topics=["dsa"], language="python")


# ---------------------------------------------------------------------------
# submit_coding_problem
# ---------------------------------------------------------------------------

class TestSubmitCodingProblem:
    def test_posts_to_correct_path_with_body(self):
        def handler(request):
            assert request.url.path == "/v1/coding-problems/cp1/submit"
            data = json.loads(request.read())
            assert data["language"] == "python"
            assert data["all_passed"] is True
            assert data["passed_count"] == 4
            assert data["total_count"] == 4
            assert "attempts" not in data
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler, user_id="uuid-1")
        client.submit_coding_problem(
            "cp1",
            language="python",
            all_passed=True,
            passed_count=4,
            total_count=4,
        )

    def test_sends_user_header(self):
        def handler(request):
            assert request.headers.get("X-Skilark-User") == "uuid-1"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler, user_id="uuid-1")
        client.submit_coding_problem(
            "cp1",
            language="python",
            all_passed=False,
            passed_count=2,
            total_count=4,
        )

    def test_raises_on_error_response(self):
        def handler(request):
            return httpx.Response(400, json={"error": "bad request"})

        client = _make_client(handler, user_id="uuid-1")
        with pytest.raises(httpx.HTTPStatusError):
            client.submit_coding_problem(
                "cp1",
                language="python",
                all_passed=True,
                passed_count=4,
                total_count=4,
            )


# ---------------------------------------------------------------------------
# send_feedback
# ---------------------------------------------------------------------------

class TestSendFeedback:
    def test_posts_to_correct_path(self):
        def handler(request):
            assert request.url.path == "/feedback"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler)
        client.send_feedback("great product")

    def test_sends_message_and_source(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["message"] == "love it"
            assert body["source"] == "cli"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler)
        client.send_feedback("love it")

    def test_custom_source(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["source"] == "custom"
            return httpx.Response(200, json={"status": "ok"})

        client = _make_client(handler)
        client.send_feedback("test", source="custom")

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

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.send_feedback("test")


# ---------------------------------------------------------------------------
# get_challenge_topics
# ---------------------------------------------------------------------------

class TestGetChallengeTopics:
    def test_returns_topic_list(self):
        def handler(request):
            assert request.url.path == "/v1/challenges/topics"
            return httpx.Response(
                200,
                json=[
                    {"topic": "python", "total": 45, "completed": 23, "correct": 18, "accuracy": 0.78},
                ],
            )

        client = _make_client(handler, user_id="uuid-1")
        topics = client.get_challenge_topics()
        assert len(topics) == 1
        assert topics[0]["topic"] == "python"


# ---------------------------------------------------------------------------
# get_coding_categories
# ---------------------------------------------------------------------------

class TestGetCodingCategories:
    def test_returns_category_list(self):
        def handler(request):
            assert request.url.path == "/v1/coding-problems/categories"
            return httpx.Response(
                200, json=[{"category": "arrays", "total": 12, "solved": 5}]
            )

        client = _make_client(handler, user_id="uuid-1")
        cats = client.get_coding_categories()
        assert len(cats) == 1
        assert cats[0]["category"] == "arrays"


# ---------------------------------------------------------------------------
# get_coding_problems_by_category
# ---------------------------------------------------------------------------

class TestGetCodingProblemsByCategory:
    def test_returns_problem_list(self):
        def handler(request):
            assert request.url.path == "/v1/coding-problems"
            assert "category=arrays" in str(request.url)
            return httpx.Response(
                200,
                json=[{"id": "p1", "title": "Two Sum", "difficulty": 1, "solved": True}],
            )

        client = _make_client(handler, user_id="uuid-1")
        problems = client.get_coding_problems_by_category("arrays")
        assert len(problems) == 1
        assert problems[0]["title"] == "Two Sum"


# ---------------------------------------------------------------------------
# get_next_challenge with topic param
# ---------------------------------------------------------------------------

class TestGetNextChallengeSingleTopic:
    def test_uses_topic_param(self):
        def handler(request):
            assert "topic=python" in str(request.url)
            assert "topics=" not in str(request.url)
            return httpx.Response(200, json={"id": "ch1", "question": "Q"})

        client = _make_client(handler, user_id="uuid-1")
        ch = client.get_next_challenge(topics=["python"], topic="python")
        assert ch["id"] == "ch1"


# ---------------------------------------------------------------------------
# get_next_coding_problem with category param
# ---------------------------------------------------------------------------

class TestGetNextCodingProblemWithCategory:
    def test_includes_category_param(self):
        def handler(request):
            assert "category=arrays" in str(request.url)
            return httpx.Response(200, json={"id": "cp1", "title": "Two Sum"})

        client = _make_client(handler, user_id="uuid-1")
        p = client.get_next_coding_problem(
            topics=["dsa"], language="python", category="arrays"
        )
        assert p["id"] == "cp1"

    def test_omits_category_when_none(self):
        def handler(request):
            assert "category" not in str(request.url)
            return httpx.Response(200, json={"id": "cp1", "title": "Two Sum"})

        client = _make_client(handler, user_id="uuid-1")
        p = client.get_next_coding_problem(topics=["dsa"], language="python")
        assert p["id"] == "cp1"


# ---------------------------------------------------------------------------
# get_coding_problem_by_id
# ---------------------------------------------------------------------------

class TestGetCodingProblemByID:
    def test_returns_problem(self):
        def handler(request):
            assert request.url.path == "/v1/coding-problems/cp-1"
            return httpx.Response(200, json={"id": "cp-1", "title": "Two Sum"})

        client = _make_client(handler, user_id="uuid-1")
        p = client.get_coding_problem_by_id("cp-1")
        assert p["id"] == "cp-1"

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

        client = _make_client(handler, user_id="uuid-1")
        assert client.get_coding_problem_by_id("missing") is None
