"""Unit tests for page settings MCP tools.

Following TDD - these tests are written before the implementation.
"""

import json
import pytest
from unittest.mock import AsyncMock, patch, MagicMock


# Helper to get tool functions by capturing them during registration
def get_tool_function(tool_name: str):
    """Get a tool function by name from the registered tools."""
    from mcp.server.fastmcp import FastMCP
    from canvas_mcp.tools.pages import register_page_tools

    mcp = FastMCP("test")
    captured_functions = {}

    original_tool = mcp.tool
    def capturing_tool(*args, **kwargs):
        decorator = original_tool(*args, **kwargs)
        def wrapper(fn):
            captured_functions[fn.__name__] = fn
            return decorator(fn)
        return wrapper

    mcp.tool = capturing_tool
    register_page_tools(mcp)

    return captured_functions.get(tool_name)


@pytest.fixture
def mock_course_id():
    """Mock get_course_id to return a fixed course ID."""
    with patch('canvas_mcp.tools.pages.get_course_id') as mock:
        mock.return_value = 67619
        yield mock


@pytest.fixture
def mock_course_code():
    """Mock get_course_code to return a readable course code."""
    with patch('canvas_mcp.tools.pages.get_course_code') as mock:
        mock.return_value = "TEST-101"
        yield mock


@pytest.fixture
def mock_canvas_request():
    """Mock make_canvas_request for API calls."""
    with patch('canvas_mcp.tools.pages.make_canvas_request') as mock:
        yield mock


# =============================================================================
# Tests for update_page_settings
# =============================================================================

class TestUpdatePageSettings:
    """Tests for the update_page_settings tool."""

    @pytest.mark.asyncio
    async def test_publish_page(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test publishing a page."""
        mock_canvas_request.return_value = {
            "url": "test-page",
            "title": "Test Page",
            "published": True,
            "front_page": False,
            "editing_roles": "teachers"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="test-page",
            published=True
        )

        assert "success" in result.lower() or "updated" in result.lower()
        assert "Test Page" in result
        mock_canvas_request.assert_called_once()
        call_args = mock_canvas_request.call_args
        assert call_args[0][0] == "put"
        assert "test-page" in call_args[0][1]

    @pytest.mark.asyncio
    async def test_unpublish_page(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test unpublishing a page."""
        mock_canvas_request.return_value = {
            "url": "test-page",
            "title": "Test Page",
            "published": False,
            "front_page": False,
            "editing_roles": "teachers"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="test-page",
            published=False
        )

        assert "success" in result.lower() or "updated" in result.lower()
        assert "Published: No" in result or "published" in result.lower()

    @pytest.mark.asyncio
    async def test_set_front_page(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test setting a page as the front page."""
        mock_canvas_request.return_value = {
            "url": "home-page",
            "title": "Home Page",
            "published": True,
            "front_page": True,
            "editing_roles": "teachers"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="home-page",
            front_page=True
        )

        assert "success" in result.lower() or "updated" in result.lower()
        assert "front page" in result.lower() or "Front Page: Yes" in result

    @pytest.mark.asyncio
    async def test_change_editing_roles(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test changing who can edit the page."""
        mock_canvas_request.return_value = {
            "url": "collab-page",
            "title": "Collaborative Page",
            "published": True,
            "front_page": False,
            "editing_roles": "teachers,students"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="collab-page",
            editing_roles="teachers,students"
        )

        assert "success" in result.lower() or "updated" in result.lower()
        # Verify the API was called with correct editing_roles
        call_args = mock_canvas_request.call_args
        assert call_args is not None

    @pytest.mark.asyncio
    async def test_no_changes_specified(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test error when no settings are specified to update."""
        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="test-page"
        )

        assert "no changes" in result.lower() or "specify" in result.lower()
        mock_canvas_request.assert_not_called()

    @pytest.mark.asyncio
    async def test_api_error_handling(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test handling of API errors."""
        mock_canvas_request.return_value = {
            "error": "Page not found"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="nonexistent-page",
            published=True
        )

        assert "error" in result.lower()

    @pytest.mark.asyncio
    async def test_cannot_unpublish_front_page(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test that unpublishing front page returns appropriate error."""
        mock_canvas_request.return_value = {
            "error": "Cannot unpublish the front page"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="front-page",
            published=False
        )

        assert "error" in result.lower() or "cannot" in result.lower()

    @pytest.mark.asyncio
    async def test_multiple_settings_update(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test updating multiple settings at once."""
        mock_canvas_request.return_value = {
            "url": "multi-update",
            "title": "Multi Update Page",
            "published": True,
            "front_page": False,
            "editing_roles": "teachers,students"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="multi-update",
            published=True,
            editing_roles="teachers,students",
            notify_of_update=True
        )

        assert "success" in result.lower() or "updated" in result.lower()
        # Verify all params were sent
        call_args = mock_canvas_request.call_args
        assert call_args is not None


# =============================================================================
# Tests for bulk_update_pages
# =============================================================================

class TestBulkUpdatePages:
    """Tests for the bulk_update_pages tool."""

    @pytest.mark.asyncio
    async def test_bulk_publish_pages(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test publishing multiple pages at once."""
        # Mock successful responses for each page
        mock_canvas_request.side_effect = [
            {"url": "page-1", "title": "Page 1", "published": True},
            {"url": "page-2", "title": "Page 2", "published": True},
            {"url": "page-3", "title": "Page 3", "published": True},
        ]

        bulk_update_pages = get_tool_function("bulk_update_pages")
        result = await bulk_update_pages(
            course_identifier="67619",
            page_urls="page-1,page-2,page-3",
            published=True
        )

        assert "3" in result or "success" in result.lower()
        assert mock_canvas_request.call_count == 3

    @pytest.mark.asyncio
    async def test_bulk_unpublish_pages(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test unpublishing multiple pages at once."""
        mock_canvas_request.side_effect = [
            {"url": "page-1", "title": "Page 1", "published": False},
            {"url": "page-2", "title": "Page 2", "published": False},
        ]

        bulk_update_pages = get_tool_function("bulk_update_pages")
        result = await bulk_update_pages(
            course_identifier="67619",
            page_urls="page-1,page-2",
            published=False
        )

        assert "2" in result or "success" in result.lower()

    @pytest.mark.asyncio
    async def test_bulk_update_partial_failure(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test handling when some pages fail to update."""
        mock_canvas_request.side_effect = [
            {"url": "page-1", "title": "Page 1", "published": True},
            {"error": "Page not found"},
            {"url": "page-3", "title": "Page 3", "published": True},
        ]

        bulk_update_pages = get_tool_function("bulk_update_pages")
        result = await bulk_update_pages(
            course_identifier="67619",
            page_urls="page-1,page-2,page-3",
            published=True
        )

        # Should report partial success
        assert "2" in result or "failed" in result.lower() or "1" in result

    @pytest.mark.asyncio
    async def test_bulk_update_empty_list(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test handling of empty page list."""
        bulk_update_pages = get_tool_function("bulk_update_pages")
        result = await bulk_update_pages(
            course_identifier="67619",
            page_urls="",
            published=True
        )

        assert "no pages" in result.lower() or "empty" in result.lower() or "specify" in result.lower()
        mock_canvas_request.assert_not_called()

    @pytest.mark.asyncio
    async def test_bulk_update_no_settings(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test error when no settings specified for bulk update."""
        bulk_update_pages = get_tool_function("bulk_update_pages")
        result = await bulk_update_pages(
            course_identifier="67619",
            page_urls="page-1,page-2"
        )

        assert "no changes" in result.lower() or "specify" in result.lower()
        mock_canvas_request.assert_not_called()


# =============================================================================
# Tests for input validation
# =============================================================================

class TestInputValidation:
    """Tests for parameter validation."""

    @pytest.mark.asyncio
    async def test_invalid_editing_roles(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test validation of editing_roles parameter."""
        mock_canvas_request.return_value = {
            "error": "Invalid editing_roles"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="test-page",
            editing_roles="invalid_role"
        )

        # Either the tool validates before calling API, or API returns error
        # Both are acceptable behaviors
        assert "error" in result.lower() or "invalid" in result.lower() or mock_canvas_request.called

    @pytest.mark.asyncio
    async def test_page_url_with_special_characters(self, mock_canvas_request, mock_course_id, mock_course_code):
        """Test handling page URLs with special characters."""
        mock_canvas_request.return_value = {
            "url": "page-with-special-chars",
            "title": "Page & Special <Chars>",
            "published": True,
            "front_page": False,
            "editing_roles": "teachers"
        }

        update_page_settings = get_tool_function("update_page_settings")
        result = await update_page_settings(
            course_identifier="67619",
            page_url_or_id="page-with-special-chars",
            published=True
        )

        assert "success" in result.lower() or "updated" in result.lower()
