"""Tests for the WatchCreateModal: form validation, API calls, dismiss behavior."""

import pytest
from unittest.mock import MagicMock

import httpx

from textual.app import App, ComposeResult
from textual.widgets import Input, Static

from skilark_cli.tui.screens.watch_create_modal import WatchCreateModal


# Default terminal size large enough to show the full modal without OutOfBounds.
_TERM_SIZE = (80, 40)


class ModalTestApp(App):
    """Minimal app that pushes WatchCreateModal on mount."""

    def __init__(self, client):
        super().__init__()
        self.client = client
        self.modal_result = None

    def compose(self) -> ComposeResult:
        yield Static("base")

    def on_mount(self) -> None:
        self.push_screen(WatchCreateModal(client=self.client), self._on_result)

    def _on_result(self, result):
        self.modal_result = result


def _mock_client():
    client = MagicMock()
    client.create_watch.return_value = {"id": "w1", "label": "Test"}
    return client


class TestWatchModalMount:
    async def test_modal_mounts(self):
        """Modal renders and label input exists."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            assert isinstance(app.screen, WatchCreateModal)
            label_input = app.screen.query_one("#watch-label-input", Input)
            assert label_input is not None

    async def test_escape_dismisses(self):
        """Escape dismisses modal with False, no API call."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            assert isinstance(app.screen, WatchCreateModal)
            await pilot.press("escape")
            await pilot.pause(delay=0.2)
            assert not isinstance(app.screen, WatchCreateModal)
            assert app.modal_result is False
            client.create_watch.assert_not_called()


class TestWatchModalSubmit:
    async def test_submit_calls_api(self):
        """Fill in fields, click Create, verify client.create_watch called."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "ML Roles"
            query_input = app.screen.query_one("#watch-query-input", Input)
            query_input.value = "ML engineer with Python and PyTorch experience"
            location_input = app.screen.query_one("#watch-location-input", Input)
            location_input.value = "San Francisco, CA"
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.5)
            client.create_watch.assert_called_once_with(
                label="ML Roles",
                query="ML engineer with Python and PyTorch experience",
                seniority="",
                company_slug="",
                location="San Francisco, CA",
            )
            assert app.modal_result is True

    async def test_empty_label_shows_error(self):
        """Click Create with empty label: no API call, error shown."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            # Leave label empty, click create
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.2)
            client.create_watch.assert_not_called()
            error = app.screen.query_one("#watch-modal-error", Static)
            assert "label" in str(error.content).lower()


class TestWatchModalValidation:
    async def test_label_too_long_shows_error(self):
        """Label over 100 chars shows error, no API call."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "x" * 101
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.2)
            client.create_watch.assert_not_called()
            error = app.screen.query_one("#watch-modal-error", Static)
            assert "100" in str(error.content)

    async def test_whitespace_only_label_shows_error(self):
        """Whitespace-only label is treated as empty."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "   "
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.2)
            client.create_watch.assert_not_called()

    async def test_query_too_short_shows_error(self):
        """Query under 10 chars shows error, no API call."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "Test"
            query_input = app.screen.query_one("#watch-query-input", Input)
            query_input.value = "short"
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.2)
            client.create_watch.assert_not_called()
            error = app.screen.query_one("#watch-modal-error", Static)
            assert "10" in str(error.content)

    async def test_empty_query_shows_error(self):
        """Empty query shows error, no API call."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "Test"
            # Leave query empty
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.2)
            client.create_watch.assert_not_called()
            error = app.screen.query_one("#watch-modal-error", Static)
            assert "query" in str(error.content).lower()

    async def test_query_too_long_shows_error(self):
        """Query over 500 chars shows error, no API call."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "Test"
            query_input = app.screen.query_one("#watch-query-input", Input)
            query_input.value = "x" * 501
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.2)
            client.create_watch.assert_not_called()
            error = app.screen.query_one("#watch-modal-error", Static)
            assert "500" in str(error.content)


class TestWatchModalErrors:
    async def test_409_shows_limit_message(self):
        """409 from API shows watch limit message."""
        client = _mock_client()
        response = httpx.Response(409, request=httpx.Request("POST", "http://test"))
        client.create_watch.side_effect = httpx.HTTPStatusError(
            "Conflict", request=response.request, response=response
        )
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "Test"
            query_input = app.screen.query_one("#watch-query-input", Input)
            query_input.value = "senior backend engineer roles"
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.5)
            error = app.screen.query_one("#watch-modal-error", Static)
            assert "limit" in str(error.content).lower()
            assert app.modal_result is None  # not dismissed

    async def test_generic_error_shows_message(self):
        """Non-409 error shows generic message."""
        client = _mock_client()
        response = httpx.Response(500, request=httpx.Request("POST", "http://test"))
        client.create_watch.side_effect = httpx.HTTPStatusError(
            "Server Error", request=response.request, response=response
        )
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            label_input = app.screen.query_one("#watch-label-input", Input)
            label_input.value = "Test"
            query_input = app.screen.query_one("#watch-query-input", Input)
            query_input.value = "senior backend engineer roles"
            await pilot.click("#watch-create-btn")
            await pilot.pause(delay=0.5)
            error = app.screen.query_one("#watch-modal-error", Static)
            assert str(error.content)  # not empty

    async def test_cancel_button_dismisses(self):
        """Cancel button dismisses with False."""
        client = _mock_client()
        app = ModalTestApp(client)
        async with app.run_test(size=_TERM_SIZE) as pilot:
            await pilot.pause(delay=0.2)
            await pilot.click("#watch-cancel-btn")
            await pilot.pause(delay=0.2)
            assert not isinstance(app.screen, WatchCreateModal)
            assert app.modal_result is False
            client.create_watch.assert_not_called()
