"""Tests for the Jobs screen (Find My Fit)."""

import pytest
from unittest.mock import MagicMock, patch

from skilark_cli.tui.app import SkilarkApp
from skilark_cli.tui.screens.jobs import JobsPane


MOCK_FIT_DATA = {
    "match_count": 3,
    "companies": [
        {
            "name": "Stripe",
            "match_count": 2,
            "closing_period": {"median_days_open": 45},
            "matches": [
                {
                    "title": "Staff Backend Engineer",
                    "url": "https://stripe.com/jobs/1",
                    "location": "San Francisco, CA",
                    "similarity": 0.92,
                    "posted_at": "2026-03-01T00:00:00Z",
                    "is_new": True,
                    "remote_policy": "hybrid",
                    "skills": ["Go", "Kubernetes", "PostgreSQL"],
                    "summary": "Design and build core payment infrastructure services.",
                },
                {
                    "title": "Senior Backend Engineer",
                    "url": "https://stripe.com/jobs/2",
                    "location": "Remote",
                    "similarity": 0.85,
                    "posted_at": "2026-02-28T00:00:00Z",
                    "is_new": False,
                    "remote_policy": "remote",
                    "skills": ["Python", "AWS"],
                },
            ],
        },
        {
            "name": "Anthropic",
            "match_count": 1,
            "closing_period": None,
            "matches": [
                {
                    "title": "Backend Engineer",
                    "url": "https://anthropic.com/jobs/1",
                    "location": "San Francisco, CA",
                    "similarity": 0.80,
                    "posted_at": "2026-03-02T00:00:00Z",
                    "is_new": False,
                    "remote_policy": "hybrid",
                    "skills": ["Python", "Rust"],
                },
            ],
        },
    ],
    "top_skills": [
        {"name": "Go", "frequency": 0.8},
        {"name": "Python", "frequency": 0.6},
    ],
    "salary_range": {"min": 160000, "max": 220000, "median": 190000},
    "common_titles": [],
    "seniority": {},
    "remote_policy": {},
    "role_categories": {},
}

MOCK_EMPTY_FIT = {
    "match_count": 0,
    "companies": [],
    "top_skills": [],
    "salary_range": None,
    "common_titles": [],
    "seniority": {},
    "remote_policy": {},
    "role_categories": {},
}


def _make_client(fit_data=None):
    client = MagicMock()
    client.find_my_fit.return_value = fit_data or MOCK_FIT_DATA
    # Drill pane also loads on mount — provide safe defaults
    client.get_next_challenge.return_value = None
    client.get_stats.return_value = {"total_completed": 0}
    return client


# --- AC1: Search input is focused on navigate ---


async def test_jobs_pane_focused_on_show():
    """AC1: Navigating to Jobs tab focuses the pane (not search input) so tab keys work."""
    app = SkilarkApp(client=_make_client())
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        pane = app.query_one(JobsPane)
        assert pane.has_focus
        # Typing a printable char redirects focus to search input
        await pilot.press("h")
        await pilot.pause()
        search = app.query_one("#jobs-search-input")
        assert search.has_focus


async def test_jobs_tab_switching_works_before_search():
    """Bug #69: Number keys should switch tabs from Jobs screen without searching first."""
    app = SkilarkApp(client=_make_client())
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        # Press 1 to switch to Drill tab — should work immediately
        await pilot.press("1")
        await pilot.pause()
        assert app._active_tab == 0  # Drill
        # Switch back to Jobs
        await pilot.press("3")
        await pilot.pause()
        assert app._active_tab == 2  # Jobs
        # Switch to Signals
        await pilot.press("2")
        await pilot.pause()
        assert app._active_tab == 1  # Signals


async def test_jobs_escape_unfocuses_search_input():
    """Escape from search input returns focus to pane, enabling tab switching."""
    app = SkilarkApp(client=_make_client())
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        # Type a char to redirect focus to search input
        await pilot.press("h")
        await pilot.pause()
        search = app.query_one("#jobs-search-input")
        assert search.has_focus
        # Press Escape to unfocus search input
        await pilot.press("escape")
        await pilot.pause()
        pane = app.query_one(JobsPane)
        assert pane.has_focus
        # Now digit keys should switch tabs
        await pilot.press("1")
        await pilot.pause(delay=0.2)
        assert app._active_tab == 0  # Drill


async def test_jobs_escape_from_empty_search_input():
    """Escape works even when search input is focused but empty."""
    app = SkilarkApp(client=_make_client())
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        # Focus search input via typing, then clear it
        await pilot.press("h")
        await pilot.pause()
        await pilot.press("backspace")
        await pilot.pause()
        search = app.query_one("#jobs-search-input")
        assert search.has_focus
        assert search.value == ""
        # Escape should still return focus to pane
        await pilot.press("escape")
        await pilot.pause()
        pane = app.query_one(JobsPane)
        assert pane.has_focus


# --- AC2: Search submits query ---


async def test_jobs_search_calls_find_my_fit():
    """AC2: Typing a query and pressing Enter calls find_my_fit."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        # Type into the focused search input
        for ch in "backend engineer":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        client.find_my_fit.assert_called_once_with("backend engineer")


# --- AC3: Results table populated ---


async def test_jobs_results_table_populated():
    """AC3: After search, results table has 3 rows (flattened from 2 companies)."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "backend":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        table = app.query_one("#jobs-results-table")
        assert table.row_count == 3


# --- AC4: Summary stats ---


async def test_jobs_summary_stats_shown():
    """AC4: Summary line shows match count and salary range."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "backend":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        stats = app.query_one("#jobs-stats")
        text = stats.content
        assert "3" in text  # match count
        assert "160" in text or "$160K" in text  # salary


# --- AC5: Detail panel updates on row select ---


async def test_jobs_detail_shows_selected_row():
    """AC5: Highlighting a row updates the detail panel with job info."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "backend":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        # First row is auto-selected (highest similarity = Staff Backend Engineer)
        detail = app.query_one("#jobs-detail")
        text = detail.content
        assert "Staff Backend Engineer" in text
        assert "Stripe" in text
        assert "payment infrastructure" in text  # summary
        assert "hybrid" in text  # remote policy
        assert "Go" in text  # skills


# --- AC6: Open URL in browser ---


async def test_jobs_open_url():
    """AC6: Pressing 'o' opens the job URL in the system browser."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "backend":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        with patch("skilark_cli.tui.screens.jobs.webbrowser.open") as mock_open:
            await pilot.press("o")
            await pilot.pause()
            mock_open.assert_called_once_with("https://stripe.com/jobs/1")


# --- AC7: Slash opens filter, Escape clears it ---


async def test_jobs_slash_opens_filter():
    """AC7: Pressing '/' opens the filter input for result filtering."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "backend":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        # Press / to open filter
        await pilot.press("slash")
        await pilot.pause()
        fi = app.query_one("#jobs-filter-input")
        assert fi.display is True
        assert fi.has_focus


async def test_jobs_filter_narrows_results():
    """Filter input narrows the results table by keyword."""
    client = _make_client()
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "backend":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        table = app.query_one("#jobs-results-table")
        assert table.row_count == 3
        # Open filter and type "Anthropic"
        await pilot.press("slash")
        await pilot.pause(delay=0.2)
        fi = app.query_one("#jobs-filter-input")
        assert fi.has_focus
        for ch in "Anthropic":
            await pilot.press(ch)
        await pilot.pause(delay=0.2)
        assert table.row_count == 1
        # Escape clears filter
        await pilot.press("escape")
        await pilot.pause(delay=0.2)
        assert table.row_count == 3


# --- AC8: Empty results ---


async def test_jobs_empty_results_message():
    """AC8: When no matches found, shows empty state message."""
    client = _make_client(fit_data=MOCK_EMPTY_FIT)
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "xyz":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        empty = app.query_one("#jobs-empty")
        assert empty.display is True
        assert "No matching" in empty.content


# --- AC9: API error handling ---


async def test_jobs_api_error_shows_message():
    """AC9: API error shows user-friendly message, not a crash."""
    import httpx
    client = MagicMock()
    client.find_my_fit.side_effect = httpx.ConnectError("Connection refused")
    client.get_next_challenge.return_value = None
    client.get_stats.return_value = {"total_completed": 0}
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "test":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.5)
        error = app.query_one("#jobs-error")
        assert error.display is True
        assert "failed" in error.content.lower() or "error" in error.content.lower()


# --- AC10: Loading state ---


async def test_jobs_loading_shown_during_search():
    """AC10: A loading indicator appears while search is in progress."""
    import threading

    barrier = threading.Event()

    client = MagicMock()
    client.get_next_challenge.return_value = None
    client.get_stats.return_value = {"total_completed": 0}

    def slow_search(query):
        barrier.wait(timeout=5)
        return MOCK_FIT_DATA

    client.find_my_fit.side_effect = slow_search
    app = SkilarkApp(client=client)
    async with app.run_test() as pilot:
        await pilot.press("3")
        await pilot.pause()
        for ch in "test":
            await pilot.press(ch)
        await pilot.press("enter")
        await pilot.pause(delay=0.1)
        # Loading should be visible
        loading = app.query_one("#jobs-loading")
        assert loading.display is True
        barrier.set()
        await pilot.pause(delay=0.5)
        # Loading should be hidden after results arrive
        assert loading.display is False
