"""Tests for the Profile screen: profile info, stats, edit, link, feedback."""

import pytest
from unittest.mock import MagicMock

from textual.widgets import Button, Input, Static, SelectionList

from skilark_cli.tui.app import SkilarkApp


MOCK_PROFILE = {
    "current_role": "swe",
    "primary_language": "Python",
    "career_direction": "stay_sharp",
    "topics": ["python", "dsa", "kubernetes"],
    "interests": [
        {"id": "i1", "name": "Python", "category": "languages_and_frameworks"},
        {"id": "i2", "name": "Kubernetes", "category": "infrastructure_and_devops"},
        {"id": "i3", "name": "FastAPI", "category": "languages_and_frameworks"},
    ],
}

MOCK_PROFILE_WITH_IDENTITY = {
    **MOCK_PROFILE,
    "display_name": "Violet User",
    "email_masked": "v***u@gmail.com",
    "auth_provider": "Google",
}

MOCK_STATS = {
    "streak": 12,
    "total_completed": 47,
    "week_completed": 8,
    "week_goal": 10,
    "topic_counts": {"python": 18, "kubernetes": 9, "dsa": 6},
    "last_session": {"date": "2026-03-06", "topic": "python", "title": "List Comprehension"},
}


MOCK_CATALOG = [
    {"id": "c1", "name": "Python", "category": "languages_and_frameworks"},
    {"id": "c2", "name": "FastAPI", "category": "languages_and_frameworks"},
    {"id": "c3", "name": "Kubernetes", "category": "infrastructure_and_devops"},
    {"id": "c4", "name": "Docker", "category": "infrastructure_and_devops"},
    {"id": "c5", "name": "System Design", "category": "computer_science"},
]

MOCK_PRESETS = [
    {
        "name": "backend_engineer",
        "label": "Backend Engineer",
        "description": "Server-side development",
        "interest_names": ["Python", "FastAPI", "Docker"],
    },
    {
        "name": "devops_engineer",
        "label": "DevOps Engineer",
        "description": "Infrastructure and operations",
        "interest_names": ["Kubernetes", "Docker"],
    },
]


def _make_client():
    client = MagicMock()
    client.get_profile.return_value = MOCK_PROFILE
    client.get_stats.return_value = MOCK_STATS
    client.create_link_code.return_value = {
        "code": "ABC123",
        "expires_at": "2026-03-07T12:10:00Z",
    }
    client.send_feedback.return_value = None
    client.update_profile.return_value = MOCK_PROFILE
    client.get_interest_catalog.return_value = MOCK_CATALOG
    client.get_interest_presets.return_value = MOCK_PRESETS
    client.update_user_interests.return_value = MOCK_PROFILE["interests"]
    return client


@pytest.fixture
def profile_app():
    client = _make_client()
    app = SkilarkApp(client=client)
    app._active_tab = 3  # Start directly on Profile
    return app


# --- AC1: Profile info displayed ---


class TestProfileInfo:
    async def test_shows_interests_grouped_by_category(self, profile_app):
        """AC1: Profile screen displays interests grouped by category."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            interests_widget = profile_app.query_one("#profile-val-topics", Static)
            text = str(interests_widget.content)
            # Should show category labels
            assert "Languages And Frameworks" in text
            assert "Infrastructure And Devops" in text
            # Should show interest names
            assert "Python" in text
            assert "Kubernetes" in text
            assert "FastAPI" in text

    async def test_shows_no_interests_message_when_empty(self):
        """Profile shows placeholder when no interests are set."""
        client = MagicMock()
        client.get_profile.return_value = {
            "current_role": "swe",
            "interests": [],
        }
        client.get_stats.return_value = MOCK_STATS
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            interests_widget = app.query_one("#profile-val-topics", Static)
            text = str(interests_widget.content).lower()
            assert "no interests set" in text

    async def test_shows_job_match_label(self, profile_app):
        """Profile shows 'Job Match' label (not 'About') for description field."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            labels = profile_app.query(".field-label")
            label_texts = [str(l.content).lower() for l in labels]
            assert any("job match" in t for t in label_texts)

    async def test_shows_description_context_hint(self, profile_app):
        """Profile shows persistent hint connecting description to Jobs tab."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            hint = profile_app.query_one("#profile-description-context", Static)
            assert hint.display is True
            text = str(hint.content).lower()
            assert "job" in text


# --- AC2: Stats displayed ---


class TestProfileStats:
    async def test_shows_streak(self, profile_app):
        """AC2: Profile screen displays streak count."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            stats = profile_app.query_one("#profile-stats", Static)
            assert "12" in str(stats.content)

    async def test_shows_weekly_progress(self, profile_app):
        """AC2: Profile screen shows weekly progress."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            stats = profile_app.query_one("#profile-stats", Static)
            text = str(stats.content)
            assert "8" in text and "10" in text

    async def test_shows_topic_bars(self, profile_app):
        """AC2: Profile screen shows per-topic breakdown."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            stats = profile_app.query_one("#profile-stats", Static)
            text = str(stats.content).lower()
            assert "python" in text
            assert "18" in str(stats.content)


# --- AC3 + AC4: Edit mode (inline) ---


class TestProfileEdit:
    async def test_edit_mode_shows_description_input(self, profile_app):
        """AC3: Pressing 'e' shows description Input and Save button."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("e")
            await pilot.pause()
            assert profile_app.query_one("#profile-edit-description", Input).display is True
            assert profile_app.query_one("#profile-edit-save", Button).display is True
            assert profile_app.query_one("#profile-val-description", Static).display is False

    async def test_edit_save_via_button(self, profile_app):
        """AC4: Clicking Save button saves description and closes edit."""
        async with profile_app.run_test(size=(120, 40)) as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("e")
            await pilot.pause()
            save_btn = profile_app.query_one("#profile-edit-save", Button)
            save_btn.focus()
            await pilot.pause()
            await pilot.press("enter")
            await pilot.pause(delay=0.5)
            assert profile_app.query_one("#profile-edit-description", Input).display is False
            assert profile_app.query_one("#profile-val-description", Static).display is True

    async def test_edit_cancel(self, profile_app):
        """AC5: Pressing Escape in edit mode cancels and restores display."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("e")
            await pilot.pause()
            await pilot.press("escape")
            await pilot.pause()
            assert profile_app.query_one("#profile-edit-description", Input).display is False
            assert profile_app.query_one("#profile-val-description", Static).display is True

    async def test_edit_refocus_after_cancel(self, profile_app):
        """After cancel, pane has focus so 'e' works again."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("e")
            await pilot.pause()
            await pilot.press("escape")
            await pilot.pause()
            await pilot.press("e")
            await pilot.pause()
            assert profile_app.query_one("#profile-edit-description", Input).display is True


# --- AC6: Link code ---


class TestProfileLink:
    async def test_link_generates_code(self, profile_app):
        """AC6: Pressing 'l' generates and displays a link code."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("l")
            await pilot.pause(delay=0.5)
            profile_app.client.create_link_code.assert_called_once()
            link = profile_app.query_one("#profile-link-code", Static)
            assert "ABC123" in str(link.content)

    async def test_link_error(self, profile_app):
        """AC9: API error during link code generation shows inline error."""
        profile_app.client.create_link_code.side_effect = Exception("Server error")
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("l")
            await pilot.pause(delay=0.5)
            link = profile_app.query_one("#profile-link-code", Static)
            text = str(link.content).lower()
            assert "failed" in text or "error" in text


# --- AC7: Feedback ---


class TestProfileFeedback:
    async def test_feedback_shows_input(self, profile_app):
        """AC7: Pressing 'f' shows the feedback input."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("f")
            await pilot.pause()
            feedback = profile_app.query_one("#profile-feedback-input", Input)
            assert feedback.display is True
            assert feedback.has_focus

    async def test_feedback_submit(self, profile_app):
        """AC7: Typing feedback and pressing Enter submits it."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("f")
            await pilot.pause()
            for ch in "great app":
                await pilot.press(ch)
            await pilot.press("enter")
            await pilot.pause(delay=0.5)
            profile_app.client.send_feedback.assert_called_once_with("great app")


# --- AC8: Fetch error ---


class TestProfileErrors:
    async def test_fetch_error(self):
        """AC8: API error during fetch shows friendly error message."""
        import httpx

        client = MagicMock()
        client.get_profile.side_effect = httpx.ConnectError("Connection refused")
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            error = app.query_one("#profile-error", Static)
            assert error.display is True
            text = str(error.content)
            assert "connect" in text.lower() or "network" in text.lower()
            assert "Connection refused" not in text

    async def test_rate_limit_shows_friendly_message(self):
        """Rate-limited fetch shows friendly message, not raw 429."""
        import httpx

        response = httpx.Response(429, request=httpx.Request("GET", "http://test"))
        client = MagicMock()
        client.get_profile.side_effect = httpx.HTTPStatusError(
            "rate limited", request=response.request, response=response
        )
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            error = app.query_one("#profile-error", Static)
            assert error.display is True
            text = str(error.content)
            assert "Slow down" in text
            assert "429" not in text


# --- Identity line (OAuth sign-in) ---


class TestProfileIdentity:
    def _make_identity_client(self):
        client = MagicMock()
        client.get_profile.return_value = MOCK_PROFILE_WITH_IDENTITY
        client.get_stats.return_value = MOCK_STATS
        client.create_link_code.return_value = {"code": "XYZ789", "expires_at": "2026-03-28T12:10:00Z"}
        client.send_feedback.return_value = None
        client.update_profile.return_value = MOCK_PROFILE_WITH_IDENTITY
        return client

    async def test_identity_shown_when_display_name_present(self):
        """Identity line appears when profile includes display_name."""
        client = self._make_identity_client()
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            identity = app.query_one("#profile-identity", Static)
            assert identity.display is True
            text = str(identity.content)
            assert "Violet User" in text

    async def test_identity_includes_provider(self):
        """Identity line includes the OAuth provider name."""
        client = self._make_identity_client()
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            identity = app.query_one("#profile-identity", Static)
            text = str(identity.content)
            assert "Google" in text

    async def test_identity_includes_masked_email(self):
        """Identity line includes the masked email address."""
        client = self._make_identity_client()
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            identity = app.query_one("#profile-identity", Static)
            text = str(identity.content)
            assert "v***u@gmail.com" in text

    async def test_identity_hidden_when_no_display_name(self, profile_app):
        """Identity line is hidden when display_name is absent."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            identity = profile_app.query_one("#profile-identity", Static)
            assert identity.display is False

    async def test_identity_without_provider_or_email(self):
        """Identity line shows only the name when provider and email are absent."""
        client = MagicMock()
        client.get_profile.return_value = {
            **MOCK_PROFILE,
            "display_name": "Sparse User",
        }
        client.get_stats.return_value = MOCK_STATS
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            identity = app.query_one("#profile-identity", Static)
            assert identity.display is True
            text = str(identity.content)
            assert "Sparse User" in text
            assert "via" not in text


# --- Interest picker ---


class TestInterestPicker:
    async def test_pressing_i_shows_picker(self, profile_app):
        """Pressing 'i' fetches catalog and shows the interest picker."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            # Picker should be hidden initially
            picker = profile_app.query_one("#interest-picker")
            assert picker.display is False

            await pilot.press("i")
            await pilot.pause(delay=0.5)

            # Picker should now be visible
            assert picker.display is True
            profile_app.client.get_interest_catalog.assert_called_once()
            profile_app.client.get_interest_presets.assert_called_once()

    async def test_picker_shows_catalog_items(self, profile_app):
        """Interest picker contains all catalog items."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            sel = profile_app.query_one("#interest-list", SelectionList)
            # All 5 catalog items + 3 category separators = 8 options
            assert sel.option_count == 8

    async def test_picker_pre_selects_current_interests(self, profile_app):
        """Current user interests are pre-selected in the picker."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            sel = profile_app.query_one("#interest-list", SelectionList)
            selected = set(sel.selected)
            # MOCK_PROFILE has Python, Kubernetes, FastAPI
            assert "Python" in selected
            assert "Kubernetes" in selected
            assert "FastAPI" in selected
            # Docker and System Design should NOT be selected
            assert "Docker" not in selected
            assert "System Design" not in selected

    async def test_preset_hides_when_user_has_interests(self, profile_app):
        """Preset bar hidden when user already has interests."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            preset_bar = profile_app.query_one("#interest-preset-bar")
            assert preset_bar.display is False

    async def test_preset_shown_when_no_interests(self):
        """Preset bar visible when user has no interests."""
        client = _make_client()
        client.get_profile.return_value = {
            "current_role": "swe",
            "interests": [],
        }
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            preset_bar = app.query_one("#interest-preset-bar")
            assert preset_bar.display is True
            # Should have preset buttons
            buttons = preset_bar.query(Button)
            assert len(buttons) >= 2

    async def test_preset_selects_interests(self):
        """Clicking a preset button selects the matching interests."""
        client = _make_client()
        client.get_profile.return_value = {
            "current_role": "swe",
            "interests": [],
        }
        app = SkilarkApp(client=client)
        app._active_tab = 3
        async with app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            # Click "Backend Engineer" preset (preset-0)
            btn = app.query_one("#preset-0", Button)
            btn.press()
            await pilot.pause(delay=0.3)

            sel = app.query_one("#interest-list", SelectionList)
            selected = set(sel.selected)
            # Backend preset: Python, FastAPI, Docker
            assert "Python" in selected
            assert "FastAPI" in selected
            assert "Docker" in selected
            # Not in backend preset
            assert "Kubernetes" not in selected

    async def test_escape_closes_picker(self, profile_app):
        """Pressing Escape closes the interest picker."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            picker = profile_app.query_one("#interest-picker")
            assert picker.display is True

            await pilot.press("escape")
            await pilot.pause()

            assert picker.display is False

    async def test_cancel_button_closes_picker(self, profile_app):
        """Cancel button closes the interest picker."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            cancel_btn = profile_app.query_one("#interest-cancel", Button)
            cancel_btn.press()
            await pilot.pause()

            picker = profile_app.query_one("#interest-picker")
            assert picker.display is False

    async def test_save_calls_api(self, profile_app):
        """Save button calls update_user_interests with selected names."""
        async with profile_app.run_test() as pilot:
            await pilot.pause(delay=0.5)
            await pilot.press("i")
            await pilot.pause(delay=0.5)

            save_btn = profile_app.query_one("#interest-save", Button)
            save_btn.press()
            await pilot.pause(delay=0.5)

            profile_app.client.update_user_interests.assert_called_once()
            # Picker should be closed after save
            picker = profile_app.query_one("#interest-picker")
            assert picker.display is False
