"""
Tests for SkilarkClient.find_my_fit() and SkilarkClient.find_my_fit_resume().

Client tests use httpx.MockTransport to intercept HTTP calls at the transport
layer — no network I/O, no filesystem access beyond the tmp_path fixture.

The _make_fit_response() and _make_resume_response() helpers are intentionally
realistic and complete so they can be re-used by later tasks (command rendering
tests, bot tests, etc.).
"""

import sys
import httpx
import pytest
from io import StringIO
from unittest.mock import MagicMock, patch
from rich.console import Console

from skilark_cli.client import SkilarkClient
from skilark_cli.display import render_fit_compact, render_fit_detail


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


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


def _make_fit_response() -> dict:
    """Return a realistic FindMyFitResponse dict.

    Mirrors the shape returned by POST /find-my-fit and POST /find-my-fit/resume
    (base fields only — the resume endpoint extends this with profile_summary
    and skill_gap).
    """
    return {
        "query": "senior backend engineer distributed systems",
        "match_count": 42,
        "companies": [
            {
                "name": "Anthropic",
                "slug": "anthropic",
                "careers_url": "https://www.anthropic.com/careers",
                "match_count": 8,
                "matches": [
                    {
                        "title": "Senior Software Engineer, Infrastructure",
                        "url": "https://boards.greenhouse.io/anthropic/jobs/123",
                        "location": "San Francisco, CA",
                    },
                    {
                        "title": "Staff Engineer, Platform",
                        "url": "https://boards.greenhouse.io/anthropic/jobs/456",
                        "location": "Remote",
                    },
                ],
            },
            {
                "name": "Stripe",
                "slug": "stripe",
                "careers_url": "https://stripe.com/jobs",
                "match_count": 5,
                "matches": [
                    {
                        "title": "Backend Engineer, Payments",
                        "url": "https://stripe.com/jobs/789",
                        "location": "New York, NY",
                    },
                ],
            },
        ],
        "common_titles": [
            {"title": "Senior Software Engineer", "url": ""},
            {"title": "Staff Engineer", "url": ""},
            {"title": "Backend Engineer", "url": ""},
        ],
        "top_skills": [
            {"name": "Go", "count": 28, "frequency": 0.67},
            {"name": "PostgreSQL", "count": 24, "frequency": 0.57},
            {"name": "Kubernetes", "count": 20, "frequency": 0.48},
            {"name": "Python", "count": 18, "frequency": 0.43},
        ],
        "salary_range": {
            "min": 180000,
            "max": 250000,
            "median": 210000,
        },
        "seniority": {
            "senior": 52,
            "staff": 29,
            "mid": 19,
        },
        "remote_policy": {
            "remote": 43,
            "hybrid": 38,
            "onsite": 19,
        },
        "role_categories": {
            "backend_engineering": 71,
            "platform": 19,
            "sre": 10,
        },
    }


def _make_resume_response() -> dict:
    """Return a realistic resume endpoint response dict.

    Extends _make_fit_response() with profile_summary and skill_gap fields
    as defined in docs/plans/2026-02-17-find-my-fit-resume-design.md.
    """
    base = _make_fit_response()
    base["profile_summary"] = {
        "skills": ["Python", "Go", "PostgreSQL", "Docker"],
        "seniority": "senior",
        "role_category": "backend_engineering",
        "years_experience": 6,
        "summary": (
            "Senior backend engineer with 6 years of experience building "
            "distributed systems. Strong background in Go and PostgreSQL."
        ),
    }
    base["skill_gap"] = {
        "summary": (
            "Most matched roles require Terraform and CI/CD pipeline experience "
            "(8 of 10 jobs). Kubernetes is frequently listed at a "
            "production-operations level, while your resume emphasises "
            "development use."
        ),
        "missing_skills": [
            {
                "name": "Terraform",
                "importance": "critical",
                "reason": "Required in 8 of 10 matched job descriptions",
            },
            {
                "name": "CI/CD Pipelines",
                "importance": "important",
                "reason": "Expected for staff-level infrastructure roles",
            },
            {
                "name": "System Design",
                "importance": "nice_to_have",
                "reason": "Mentioned in interview requirements for senior+ roles",
            },
        ],
        "underrepresented": [
            {
                "name": "Kubernetes (operations)",
                "importance": "important",
                "reason": "Listed at production-operations level",
            },
            {
                "name": "Leadership / Mentoring",
                "importance": "nice_to_have",
                "reason": "Expected for staff-level roles",
            },
        ],
        "recommendation": (
            "Focus on Terraform and a CI/CD tool (GitHub Actions or "
            "CircleCI) to close the highest-priority gaps."
        ),
    }
    return base


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


class TestFindMyFit:
    def test_posts_to_correct_path(self):
        """find_my_fit() must POST to /find-my-fit (no /v1 prefix)."""
        def handler(request):
            assert request.url.path == "/find-my-fit"
            return httpx.Response(200, json=_make_fit_response())

        client = _make_client(handler)
        client.find_my_fit("senior backend engineer")

    def test_sends_query_in_json_body(self):
        """find_my_fit() encodes the query string as JSON body {"query": ...}."""
        captured = {}

        def handler(request):
            captured["body"] = request.read()
            captured["content_type"] = request.headers.get("content-type", "")
            return httpx.Response(200, json=_make_fit_response())

        client = _make_client(handler)
        client.find_my_fit("senior backend engineer distributed systems")

        import json
        body = json.loads(captured["body"])
        assert body["query"] == "senior backend engineer distributed systems"
        assert "application/json" in captured["content_type"]

    def test_returns_parsed_json(self):
        """find_my_fit() returns the parsed response dict."""
        def handler(request):
            return httpx.Response(200, json=_make_fit_response())

        client = _make_client(handler)
        result = client.find_my_fit("backend engineer")

        assert result["query"] == "senior backend engineer distributed systems"
        assert result["match_count"] == 42
        assert isinstance(result["companies"], list)
        assert len(result["companies"]) == 2
        assert result["companies"][0]["name"] == "Anthropic"
        assert isinstance(result["top_skills"], list)
        assert isinstance(result["salary_range"], dict)

    def test_raises_on_server_error(self):
        """find_my_fit() propagates HTTP 500 as HTTPStatusError."""
        def handler(request):
            return httpx.Response(500, json={"error": "internal server error"})

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.find_my_fit("backend engineer")

    def test_raises_on_bad_request(self):
        """find_my_fit() propagates HTTP 400 as HTTPStatusError."""
        def handler(request):
            return httpx.Response(400, json={"error": "query is required"})

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


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


class TestFindMyFitResume:
    def test_posts_to_correct_path(self, tmp_path):
        """find_my_fit_resume() must POST to /find-my-fit/resume."""
        pdf_path = tmp_path / "resume.pdf"
        pdf_path.write_bytes(b"%PDF-1.4 fake content")

        def handler(request):
            assert request.url.path == "/find-my-fit/resume"
            return httpx.Response(200, json=_make_resume_response())

        client = _make_client(handler)
        client.find_my_fit_resume(str(pdf_path))

    def test_sends_multipart_with_resume_field(self, tmp_path):
        """find_my_fit_resume() sends multipart/form-data with a 'resume' field."""
        pdf_content = b"%PDF-1.4 fake resume content"
        pdf_path = tmp_path / "my_cv.pdf"
        pdf_path.write_bytes(pdf_content)

        captured = {}

        def handler(request):
            captured["content_type"] = request.headers.get("content-type", "")
            captured["body"] = request.read()
            return httpx.Response(200, json=_make_resume_response())

        client = _make_client(handler)
        client.find_my_fit_resume(str(pdf_path))

        # multipart/form-data content type header
        assert "multipart/form-data" in captured["content_type"]
        # The field name 'resume' and the file content must appear in the body
        assert b"resume" in captured["body"]
        assert pdf_content in captured["body"]

    def test_returns_resume_response_with_profile_and_gap(self, tmp_path):
        """find_my_fit_resume() returns parsed dict including profile_summary and skill_gap."""
        pdf_path = tmp_path / "resume.pdf"
        pdf_path.write_bytes(b"%PDF-1.4 fake content")

        def handler(request):
            return httpx.Response(200, json=_make_resume_response())

        client = _make_client(handler)
        result = client.find_my_fit_resume(str(pdf_path))

        # Base find-my-fit fields
        assert result["match_count"] == 42
        assert isinstance(result["companies"], list)

        # Profile summary
        assert "profile_summary" in result
        profile = result["profile_summary"]
        assert isinstance(profile["skills"], list)
        assert profile["seniority"] == "senior"
        assert profile["years_experience"] == 6
        assert "summary" in profile

        # Skill gap
        assert "skill_gap" in result
        gap = result["skill_gap"]
        assert "summary" in gap
        assert isinstance(gap["missing_skills"], list)
        assert gap["missing_skills"][0]["name"] == "Terraform"
        assert "importance" in gap["missing_skills"][0]
        assert "reason" in gap["missing_skills"][0]
        assert isinstance(gap["underrepresented"], list)

    def test_raises_on_413(self, tmp_path):
        """find_my_fit_resume() propagates HTTP 413 (file too large) as HTTPStatusError."""
        pdf_path = tmp_path / "big.pdf"
        pdf_path.write_bytes(b"%PDF-1.4 " + b"x" * 100)

        def handler(request):
            return httpx.Response(413, json={"error": "file exceeds 5MB limit"})

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.find_my_fit_resume(str(pdf_path))

    def test_raises_on_503_budget(self, tmp_path):
        """find_my_fit_resume() propagates HTTP 503 (daily budget exhausted) as HTTPStatusError."""
        pdf_path = tmp_path / "resume.pdf"
        pdf_path.write_bytes(b"%PDF-1.4 fake content")

        def handler(request):
            return httpx.Response(
                503,
                json={
                    "error": (
                        "Resume analysis is temporarily unavailable. "
                        "Please try again tomorrow or use the text search."
                    )
                },
            )

        client = _make_client(handler)
        with pytest.raises(httpx.HTTPStatusError):
            client.find_my_fit_resume(str(pdf_path))


# ---------------------------------------------------------------------------
# Display helper: render_fit_compact and render_fit_detail
# ---------------------------------------------------------------------------


def _capture_output(render_fn, data: dict) -> str:
    """Call a render function and capture its console output as a string.

    Uses no_color=True so Rich strips all ANSI escape codes, making plain
    string assertions reliable regardless of markup structure.
    """
    buf = StringIO()
    console = Console(file=buf, no_color=True, highlight=False, width=120)
    render_fn(console, data)
    return buf.getvalue()


class TestRenderFitCompact:
    def test_shows_match_count(self):
        output = _capture_output(render_fit_compact, _make_fit_response())
        assert "42 matches" in output

    def test_shows_salary_range(self):
        output = _capture_output(render_fit_compact, _make_fit_response())
        assert "$180K" in output
        assert "$250K" in output
        assert "median" in output.lower()

    def test_shows_top_skills(self):
        # Go has frequency=0.67 → 67%
        output = _capture_output(render_fit_compact, _make_fit_response())
        assert "Go" in output
        assert "67%" in output

    def test_shows_company_names_and_counts(self):
        output = _capture_output(render_fit_compact, _make_fit_response())
        assert "Stripe" in output
        assert "5 matches" in output

    def test_shows_matched_positions(self):
        output = _capture_output(render_fit_compact, _make_fit_response())
        assert "Senior Software Engineer, Infrastructure" in output
        assert "Anthropic" in output

    def test_positions_most_relevant_first(self):
        """Most relevant match (first from API) should appear first in output."""
        output = _capture_output(render_fit_compact, _make_fit_response())
        # API returns Anthropic first (most relevant)
        anthropic_pos = output.index("Senior Software Engineer, Infrastructure")
        stripe_pos = output.index("Backend Engineer, Payments")
        assert anthropic_pos < stripe_pos

    def test_shows_profile_summary_for_resume(self):
        output = _capture_output(render_fit_compact, _make_resume_response())
        assert "Senior" in output
        assert "6 yr" in output

    def test_shows_skill_gaps_for_resume(self):
        output = _capture_output(render_fit_compact, _make_resume_response())
        assert "Terraform" in output
        assert "critical" in output.lower()

    def test_no_profile_for_text_search(self):
        output = _capture_output(render_fit_compact, _make_fit_response())
        assert "Your Profile" not in output
        assert "Skill Gap" not in output

    def test_no_salary_when_missing(self):
        data = _make_fit_response()
        data["salary_range"] = None
        output = _capture_output(render_fit_compact, data)
        assert "Salary" not in output

    def test_zero_matches(self):
        data = _make_fit_response()
        data["match_count"] = 0
        data["companies"] = []
        data["common_titles"] = []
        data["top_skills"] = []
        output = _capture_output(render_fit_compact, data)
        assert "No matching positions" in output


class TestRenderFitDetail:
    def test_shows_seniority_breakdown(self):
        # seniority values are already percentages from the API
        output = _capture_output(render_fit_detail, _make_fit_response())
        assert "senior" in output.lower()
        assert "52%" in output

    def test_shows_remote_policy_breakdown(self):
        output = _capture_output(render_fit_detail, _make_fit_response())
        assert "hybrid" in output.lower()
        assert "38%" in output

    def test_shows_role_category_breakdown(self):
        output = _capture_output(render_fit_detail, _make_fit_response())
        assert "backend" in output.lower()

    def test_shows_individual_positions_with_company_and_location(self):
        output = _capture_output(render_fit_detail, _make_fit_response())
        assert "Backend Engineer, Payments" in output
        assert "Stripe" in output
        assert "New York, NY" in output

    def test_shows_all_skill_gaps_with_reasons_for_resume(self):
        output = _capture_output(render_fit_detail, _make_resume_response())
        assert "Terraform" in output
        assert "Required in 8 of 10 matched job descriptions" in output

    def test_shows_underrepresented_skills_for_resume(self):
        output = _capture_output(render_fit_detail, _make_resume_response())
        assert "Kubernetes (operations)" in output
        assert "Listed at production-operations level" in output

    def test_shows_recommendation_for_resume(self):
        output = _capture_output(render_fit_detail, _make_resume_response())
        assert "Terraform" in output
        assert "CI/CD" in output


# ---------------------------------------------------------------------------
# run_fit — command logic tests
# ---------------------------------------------------------------------------


class TestRunFit:
    def test_text_search_calls_find_my_fit(self):
        """run_fit with a query calls client.find_my_fit with that query."""
        mock_client = MagicMock()
        mock_client.find_my_fit.return_value = _make_fit_response()

        mock_console = MagicMock()
        mock_console.status.return_value.__enter__ = MagicMock(return_value=None)
        mock_console.status.return_value.__exit__ = MagicMock(return_value=False)

        with patch("skilark_cli.commands.fit.console", mock_console):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query="senior backend engineer", resume_path=None, detail=False)

        mock_client.find_my_fit.assert_called_once_with("senior backend engineer")
        mock_client.find_my_fit_resume.assert_not_called()

    def test_resume_calls_find_my_fit_resume(self, tmp_path):
        """run_fit with a resume_path calls client.find_my_fit_resume."""
        pdf_path = tmp_path / "resume.pdf"
        pdf_path.write_bytes(b"%PDF-1.4 fake content")

        mock_client = MagicMock()
        mock_client.find_my_fit_resume.return_value = _make_resume_response()

        mock_console = MagicMock()
        mock_console.status.return_value.__enter__ = MagicMock(return_value=None)
        mock_console.status.return_value.__exit__ = MagicMock(return_value=False)

        with patch("skilark_cli.commands.fit.console", mock_console):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query=None, resume_path=str(pdf_path), detail=False)

        mock_client.find_my_fit_resume.assert_called_once_with(str(pdf_path))
        mock_client.find_my_fit.assert_not_called()

    def test_compact_calls_render_fit_compact(self):
        """run_fit with detail=False calls render_fit_compact, not render_fit_detail."""
        mock_client = MagicMock()
        mock_client.find_my_fit.return_value = _make_fit_response()

        mock_console = MagicMock()
        mock_console.status.return_value.__enter__ = MagicMock(return_value=None)
        mock_console.status.return_value.__exit__ = MagicMock(return_value=False)

        with (
            patch("skilark_cli.commands.fit.console", mock_console),
            patch("skilark_cli.commands.fit.render_fit_compact") as mock_compact,
            patch("skilark_cli.commands.fit.render_fit_detail") as mock_detail,
        ):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query="golang", resume_path=None, detail=False)

        mock_compact.assert_called_once()
        mock_detail.assert_not_called()

    def test_detail_calls_render_fit_detail(self):
        """run_fit with detail=True calls render_fit_detail, not render_fit_compact."""
        mock_client = MagicMock()
        mock_client.find_my_fit.return_value = _make_fit_response()

        mock_console = MagicMock()
        mock_console.status.return_value.__enter__ = MagicMock(return_value=None)
        mock_console.status.return_value.__exit__ = MagicMock(return_value=False)

        with (
            patch("skilark_cli.commands.fit.console", mock_console),
            patch("skilark_cli.commands.fit.render_fit_compact") as mock_compact,
            patch("skilark_cli.commands.fit.render_fit_detail") as mock_detail,
        ):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query="golang", resume_path=None, detail=True)

        mock_detail.assert_called_once()
        mock_compact.assert_not_called()

    def test_shows_spinner_during_search(self):
        """run_fit uses console.status as a context manager (spinner)."""
        mock_client = MagicMock()
        mock_client.find_my_fit.return_value = _make_fit_response()

        mock_console = MagicMock()
        mock_console.status.return_value.__enter__ = MagicMock(return_value=None)
        mock_console.status.return_value.__exit__ = MagicMock(return_value=False)

        with (
            patch("skilark_cli.commands.fit.console", mock_console),
            patch("skilark_cli.commands.fit.render_fit_compact"),
        ):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query="golang", resume_path=None, detail=False)

        mock_console.status.assert_called_once()

    def test_file_not_found_prints_error(self):
        """run_fit prints error and skips API call when resume path doesn't exist."""
        mock_client = MagicMock()

        mock_console = MagicMock()

        with patch("skilark_cli.commands.fit.console", mock_console):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query=None, resume_path="/nonexistent/resume.pdf", detail=False)

        mock_client.find_my_fit_resume.assert_not_called()
        mock_client.find_my_fit.assert_not_called()
        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "not found" in all_output.lower() or "File not found" in all_output

    def test_non_pdf_prints_error(self, tmp_path):
        """run_fit prints error and skips API call when resume is not a .pdf file."""
        txt_path = tmp_path / "resume.txt"
        txt_path.write_text("not a pdf")

        mock_client = MagicMock()
        mock_console = MagicMock()

        with patch("skilark_cli.commands.fit.console", mock_console):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query=None, resume_path=str(txt_path), detail=False)

        mock_client.find_my_fit_resume.assert_not_called()
        mock_client.find_my_fit.assert_not_called()
        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "PDF" in all_output or "pdf" in all_output.lower()

    def test_api_error_prints_error(self):
        """run_fit catches HTTPStatusError and prints a user-friendly message."""
        mock_client = MagicMock()
        mock_client.find_my_fit.side_effect = httpx.HTTPStatusError(
            "503", request=MagicMock(), response=MagicMock(status_code=503)
        )

        mock_console = MagicMock()
        mock_console.status.return_value.__enter__ = MagicMock(return_value=None)
        mock_console.status.return_value.__exit__ = MagicMock(return_value=False)

        with patch("skilark_cli.commands.fit.console", mock_console):
            from skilark_cli.commands.fit import run_fit
            run_fit(mock_client, query="golang", resume_path=None, detail=False)

        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "unavailable" in all_output.lower() or "failed" in all_output.lower()


# ---------------------------------------------------------------------------
# dispatch() — config/first-run gate and argument parsing
# ---------------------------------------------------------------------------


class TestFitDispatch:
    _mock_config = {"user_id": "uuid-1", "topics": ["python"], "api_url": "https://test"}

    def _make_store(self, first_run: bool = False):
        mock_store = MagicMock()
        mock_store.is_first_run.return_value = first_run
        mock_store.load.return_value = self._mock_config
        return mock_store

    def test_text_query(self):
        """dispatch(['senior', 'backend']) passes query='senior backend' to run_fit."""
        mock_store = self._make_store()
        mock_client = MagicMock()

        with (
            patch("skilark_cli.commands.fit.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.fit.SkilarkClient", return_value=mock_client),
            patch("skilark_cli.commands.fit.run_fit") as mock_run,
        ):
            from skilark_cli.commands.fit import dispatch
            dispatch(["senior", "backend"])

        mock_run.assert_called_once_with(
            mock_client, query="senior backend", resume_path=None, detail=False
        )

    def test_resume_flag(self):
        """dispatch(['--resume', '/path.pdf']) passes resume_path='/path.pdf' to run_fit."""
        mock_store = self._make_store()
        mock_client = MagicMock()

        with (
            patch("skilark_cli.commands.fit.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.fit.SkilarkClient", return_value=mock_client),
            patch("skilark_cli.commands.fit.run_fit") as mock_run,
        ):
            from skilark_cli.commands.fit import dispatch
            dispatch(["--resume", "/path.pdf"])

        mock_run.assert_called_once_with(
            mock_client, query=None, resume_path="/path.pdf", detail=False
        )

    def test_detail_flag(self):
        """dispatch(['golang', '--detail']) passes detail=True and query='golang' to run_fit."""
        mock_store = self._make_store()
        mock_client = MagicMock()

        with (
            patch("skilark_cli.commands.fit.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.fit.SkilarkClient", return_value=mock_client),
            patch("skilark_cli.commands.fit.run_fit") as mock_run,
        ):
            from skilark_cli.commands.fit import dispatch
            dispatch(["golang", "--detail"])

        mock_run.assert_called_once_with(
            mock_client, query="golang", resume_path=None, detail=True
        )

    def test_no_args_prints_usage(self):
        """dispatch([]) prints usage text without calling the API."""
        mock_store = self._make_store()
        mock_console = MagicMock()

        with (
            patch("skilark_cli.commands.fit.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.fit.console", mock_console),
            patch("skilark_cli.commands.fit.run_fit") as mock_run,
        ):
            from skilark_cli.commands.fit import dispatch
            dispatch([])

        mock_run.assert_not_called()
        all_output = " ".join(str(c) for c in mock_console.print.call_args_list)
        assert "Usage" in all_output or "usage" in all_output.lower()

    def test_first_run_exits_with_setup_hint(self):
        """dispatch() exits cleanly with a setup hint when no config exists."""
        mock_store = self._make_store(first_run=True)
        mock_console = MagicMock()

        with (
            patch("skilark_cli.commands.fit.ConfigStore", return_value=mock_store),
            patch("skilark_cli.commands.fit.console", mock_console),
            patch("skilark_cli.commands.fit.run_fit") as mock_run,
        ):
            from skilark_cli.commands.fit import dispatch
            dispatch([])

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


# ---------------------------------------------------------------------------
# main.py routing — fit command
# ---------------------------------------------------------------------------


class TestMainFitRouting:
    def test_fit_routes_to_dispatch(self):
        """main() routes `fit golang backend` to fit.dispatch(['golang', 'backend'])."""
        from skilark_cli.main import main

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

        mock_dispatch.assert_called_once_with(["golang", "backend"])

    def test_fit_resume_routes_to_dispatch(self):
        """main() routes `fit --resume file.pdf` to fit.dispatch(['--resume', 'file.pdf'])."""
        from skilark_cli.main import main

        with (
            patch("skilark_cli.commands.fit.dispatch") as mock_dispatch,
            patch.object(sys, "argv", ["skilark", "fit", "--resume", "file.pdf"]),
        ):
            main()

        mock_dispatch.assert_called_once_with(["--resume", "file.pdf"])
