"""
Integration tests for the Redmine MCP server.

This module contains integration tests that test the actual connection
to Redmine and the overall functionality of the MCP server.
"""

import os
import sys

import pytest

# Add the src directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from redmine_mcp_server._client import (  # noqa: E402
    _get_redmine_client,
    REDMINE_URL,
)
from redmine_mcp_server.tools.time_tracking import (  # noqa: E402
    list_time_entry_activities,
)


def _get_redmine_or_none():
    """Try to get a Redmine client, return None if not configured."""
    try:
        return _get_redmine_client()
    except RuntimeError:
        return None


def _integration_test_custom_fields():
    """Return required custom fields for integration test issue creation.

    Reads from INTEGRATION_TEST_CUSTOM_FIELDS env var (JSON), falling back
    to a sensible default for the test Redmine instance.
    """
    import json

    env_val = os.getenv("INTEGRATION_TEST_CUSTOM_FIELDS")
    if env_val:
        return json.loads(env_val)
    # Default: Department (id=2) is required on the test Redmine server
    return {"custom_fields": [{"id": 2, "value": "Engineering"}]}


def _get_activity_id(redmine):
    """Return the first available time entry activity ID, or None."""
    try:
        activities = list(redmine.enumeration.filter(resource="time_entry_activities"))
        return activities[0].id if activities else None
    except Exception:
        return None


class TestRedmineIntegration:
    """Integration tests for Redmine connectivity."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    def test_redmine_connection(self):
        """Test actual connection to Redmine server."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        try:
            # Try to access projects - this will test authentication
            projects = redmine.project.all()
            assert projects is not None
            project_count = len(list(projects))
            print(f"Successfully connected to Redmine. Found {project_count} projects.")
        except Exception as e:
            pytest.fail(f"Failed to connect to Redmine: {e}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_projects_integration(self):
        """Integration test for listing projects."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_projects

        result = await list_redmine_projects()

        assert result is not None
        assert isinstance(result, list)

        if len(result) > 0:
            # Verify structure of first project
            project = result[0]
            assert "id" in project
            assert "name" in project
            assert "identifier" in project
            assert "description" in project
            assert "created_on" in project

            assert isinstance(project["id"], int)
            assert isinstance(project["name"], str)
            assert isinstance(project["identifier"], str)

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_get_issue_integration(self):
        """Integration test for getting an issue with journals and attachments."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import (
            get_redmine_issue,
        )  # First, try to get any issue to test with

        try:
            # Get the first project and see if it has issues
            projects = redmine.project.all()
            if not projects:
                pytest.skip("No projects found for testing")

            # Try to find an issue in any project
            test_issue_id = None
            for project in projects:
                try:
                    issues = redmine.issue.filter(project_id=project.id, limit=1)
                    if issues:
                        test_issue_id = issues[0].id
                        break
                except Exception:
                    continue

            if test_issue_id is None:
                pytest.skip("No issues found for testing")

            # Test getting the issue including journals and attachments by default
            result = await get_redmine_issue(test_issue_id)

            assert result is not None
            assert "id" in result
            assert "subject" in result
            assert "project" in result
            assert "status" in result
            assert "priority" in result
            assert "author" in result

            assert result["id"] == test_issue_id
            assert isinstance(result["subject"], str)
            assert isinstance(result["project"], dict)
            assert isinstance(result["status"], dict)
            assert "journals" in result
            assert isinstance(result["journals"], list)
            assert "attachments" in result
            assert isinstance(result["attachments"], list)

        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_get_issue_without_journals_integration(self):
        """Integration test for opting out of journal retrieval."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import get_redmine_issue

        try:
            projects = redmine.project.all()
            if not projects:
                pytest.skip("No projects found for testing")

            test_issue_id = None
            for project in projects:
                try:
                    issues = redmine.issue.filter(project_id=project.id, limit=1)
                    if issues:
                        test_issue_id = issues[0].id
                        break
                except Exception:
                    continue

            if test_issue_id is None:
                pytest.skip("No issues found for testing")

            result = await get_redmine_issue(test_issue_id, include_journals=False)

            assert result is not None
            assert "journals" not in result
            assert "attachments" in result
            assert isinstance(result["attachments"], list)

        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_get_issue_without_attachments_integration(self):
        """Integration test for opting out of attachment retrieval."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import get_redmine_issue

        try:
            projects = redmine.project.all()
            if not projects:
                pytest.skip("No projects found for testing")

            test_issue_id = None
            for project in projects:
                try:
                    issues = redmine.issue.filter(project_id=project.id, limit=1)
                    if issues:
                        test_issue_id = issues[0].id
                        break
                except Exception:
                    continue

            if test_issue_id is None:
                pytest.skip("No issues found for testing")

            result = await get_redmine_issue(test_issue_id, include_attachments=False)

            assert result is not None
            assert "attachments" not in result

        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_create_update_issue_integration(self):
        """Integration test for creating and updating an issue."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import (  # noqa: E402
            create_redmine_issue,
            update_redmine_issue,
        )

        # Pick the first available project
        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")
        project_id = projects[0].id

        issue_id = None
        try:
            # Create a new issue
            new_subject = "Integration Test Issue"
            issue = await create_redmine_issue(
                project_id,
                new_subject,
                "Created by integration test",
                extra_fields=_integration_test_custom_fields(),
            )
            assert issue and "id" in issue
            issue_id = issue["id"]

            # Update the issue
            updated_subject = new_subject + " Updated"
            updated = await update_redmine_issue(issue_id, {"subject": updated_subject})
            assert updated["id"] == issue_id
            assert updated["subject"] == updated_subject
        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")
        finally:
            # Clean up the created issue if possible
            if issue_id is not None:
                try:
                    redmine.issue.delete(issue_id)
                except Exception as e:
                    pytest.fail(f"Integration test failed: {e}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_download_attachment_integration(self, tmp_path):
        """Integration test for downloading an attachment."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.files import (
            get_redmine_attachment,
        )
        from redmine_mcp_server.tools.issues import create_redmine_issue
        import tempfile
        import os

        # Pick the first available project
        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")
        project_id = projects[0].id

        issue_id = None
        attachment_id = None

        try:
            # Create a test file to attach
            with tempfile.NamedTemporaryFile(
                mode="w", suffix=".txt", delete=False
            ) as test_file:
                test_file.write("This is a test attachment for integration testing.\n")
                test_file.write("Created by the MCP Redmine integration test suite.\n")
                test_file_path = test_file.name

            try:
                # Create a new issue
                new_subject = "Integration Test Issue with Attachment"
                issue = await create_redmine_issue(
                    project_id,
                    new_subject,
                    "Testing attachment download functionality",
                    extra_fields=_integration_test_custom_fields(),
                )
                assert issue and "id" in issue
                issue_id = issue["id"]

                # Upload the attachment to the issue
                # First, we need to upload the file to get a token
                import requests
                from requests.auth import HTTPBasicAuth

                upload_url = f"{REDMINE_URL}/uploads.json"

                # Use API key if available, otherwise use basic auth
                api_key = os.getenv("REDMINE_API_KEY")
                username = os.getenv("REDMINE_USERNAME")
                password = os.getenv("REDMINE_PASSWORD")

                if api_key:
                    headers = {"X-Redmine-API-Key": api_key}
                    auth = None
                else:
                    headers = {}
                    auth = HTTPBasicAuth(username, password)

                with open(test_file_path, "rb") as f:
                    # Read file content
                    file_content = f.read()

                # Set content-type header for file upload
                headers["Content-Type"] = "application/octet-stream"

                # Upload file directly as binary data
                response = requests.post(
                    upload_url,
                    headers=headers,
                    data=file_content,
                    auth=auth,
                    params={"filename": os.path.basename(test_file_path)},
                )

                if response.status_code != 201:
                    skip_msg = (
                        f"Failed to upload attachment: "
                        f"{response.status_code} - {response.text}"
                    )
                    pytest.skip(skip_msg)

                upload_token = response.json()["upload"]["token"]

                # Now update the issue to include the attachment
                redmine.issue.update(
                    issue_id,
                    uploads=[
                        {
                            "token": upload_token,
                            "filename": os.path.basename(test_file_path),
                        }
                    ],
                )

                # Get the issue with attachments to find the attachment ID
                issue_with_attachments = redmine.issue.get(
                    issue_id, include=["attachments"]
                )
                if not issue_with_attachments.attachments:
                    pytest.skip("Failed to create attachment for testing")

                attachment_id = issue_with_attachments.attachments[0].id

            finally:
                # Clean up the temporary file
                if os.path.exists(test_file_path):
                    os.unlink(test_file_path)

            # Now test downloading the attachment
            result = await get_redmine_attachment(attachment_id)

            # Test the API format (uri or file_path depending on mode)
            assert "uri_type" in result
            assert "filename" in result
            assert "content_type" in result
            assert "size" in result
            assert "expires_at" in result
            assert "attachment_id" in result
            assert result["attachment_id"] == attachment_id

            # In HTTP mode a URI is returned; in stdio mode a file_path is returned
            assert "uri" in result or "file_path" in result

            # Verify file was actually downloaded to the attachments directory
            attachments_dir = "attachments"
            if os.path.exists(attachments_dir):
                # Check that some file was created (UUID directory structure)
                has_files = any(
                    os.path.isdir(os.path.join(attachments_dir, item))
                    for item in os.listdir(attachments_dir)
                )
                assert has_files, "No attachment files were created"

        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")
        finally:
            # Clean up the created issue
            if issue_id:
                try:
                    redmine.issue.delete(issue_id)
                except Exception:
                    pass  # Best effort cleanup

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_wiki_page_lifecycle_integration(self):
        """Integration test for creating, updating, and deleting a wiki page."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.wiki import (
            manage_redmine_wiki_page,
        )  # Pick the first available project

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].identifier
        wiki_title = "Integration_Test_Wiki_Page"

        try:
            # 1. Create a new wiki page
            create_result = await manage_redmine_wiki_page(
                action="create",
                project_id=project_id,
                wiki_page_title=wiki_title,
                text="# Integration Test\n\nCreated by integration tests.",
                comments="Initial creation by integration test",
            )

            # Check for permission errors (some projects may not allow wiki editing)
            if "error" in create_result:
                if (
                    "denied" in create_result["error"].lower()
                    or "permission" in create_result["error"].lower()
                    or "forbidden" in create_result["error"].lower()
                ):
                    pytest.skip(f"Wiki editing not permitted: {create_result['error']}")
                pytest.fail(f"Failed to create wiki page: {create_result['error']}")

            assert create_result["title"] == wiki_title
            assert "Integration Test" in create_result["text"]
            assert create_result["version"] == 1

            # 2. Verify the page was created by reading it
            read_result = await manage_redmine_wiki_page(
                action="get",
                project_id=project_id,
                wiki_page_title=wiki_title,
            )
            assert "error" not in read_result
            assert read_result["title"] == wiki_title

            # 3. Update the wiki page
            update_result = await manage_redmine_wiki_page(
                action="update",
                project_id=project_id,
                wiki_page_title=wiki_title,
                text="# Integration Test Updated\n\nUpdated by integration tests.",
                comments="Updated by integration test",
            )

            if "error" in update_result:
                pytest.fail(f"Failed to update wiki page: {update_result['error']}")

            assert update_result["title"] == wiki_title
            assert "Updated" in update_result["text"]
            assert update_result["version"] >= 2  # Version should increment

            # 4. Delete the wiki page
            delete_result = await manage_redmine_wiki_page(
                action="delete",
                project_id=project_id,
                wiki_page_title=wiki_title,
            )

            if "error" in delete_result:
                pytest.fail(f"Failed to delete wiki page: {delete_result['error']}")

            assert delete_result["success"] is True
            assert delete_result["title"] == wiki_title

            # 5. Verify the page was deleted
            verify_result = await manage_redmine_wiki_page(
                action="get",
                project_id=project_id,
                wiki_page_title=wiki_title,
            )
            assert "error" in verify_result
            assert "not found" in verify_result["error"].lower()

        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")
        finally:
            # Clean up: attempt to delete the wiki page if it still exists
            try:
                await manage_redmine_wiki_page(
                    action="delete",
                    project_id=project_id,
                    wiki_page_title=wiki_title,
                )
            except Exception:
                pass  # Best effort cleanup

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_wiki_page_delete_not_found_integration(self):
        """Integration test for deleting a non-existent wiki page."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.wiki import (
            manage_redmine_wiki_page,
        )  # Pick the first available project

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].identifier
        nonexistent_title = "Nonexistent_Wiki_Page_Delete_Test_99999"

        # Test delete on non-existent page - should return error
        # Note: Redmine's wiki update API has upsert behavior (creates if not exists),
        # so we only test delete for "not found" errors
        delete_result = await manage_redmine_wiki_page(
            action="delete",
            project_id=project_id,
            wiki_page_title=nonexistent_title,
        )
        assert "error" in delete_result
        assert "not found" in delete_result["error"].lower()


class TestFastAPIIntegration:
    """Integration tests for the FastAPI server."""

    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_fastapi_health(self):
        """Test that the FastAPI server can start and respond."""
        # This test would require the server to be running
        # For now, we'll test the app creation
        from redmine_mcp_server.main import app

        assert app is not None
        assert hasattr(app, "router")

    @pytest.mark.integration
    def test_mcp_endpoint_exists(self):
        """Test that the MCP endpoint is properly configured."""
        from redmine_mcp_server.main import app

        # Check that routes are configured
        route_paths = [
            route.path for route in app.router.routes if hasattr(route, "path")
        ]

        # Should have the MCP endpoint (replaced SSE)
        assert (
            "/mcp" in route_paths
        ), f"MCP endpoint not found. Available routes: {route_paths}"

    @pytest.mark.integration
    def test_health_endpoint_exists(self):
        """Test that the health check endpoint is configured."""
        from redmine_mcp_server.main import app

        route_paths = [
            route.path for route in app.router.routes if hasattr(route, "path")
        ]

        assert (
            "/health" in route_paths
        ), f"Health endpoint not found. Available routes: {route_paths}"


class TestListRedmineIssuesIntegration:
    """Integration tests for list_redmine_issues tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_by_project(self):
        """Test listing issues filtered by project_id."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].id
        result = await list_redmine_issues(project_id=project_id)

        assert isinstance(result, list)
        # All returned issues should belong to the requested project
        for issue in result:
            if "error" in issue:
                pytest.fail(f"API error: {issue['error']}")
            assert issue["project"]["id"] == project_id

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_by_string_identifier(self):
        """Test listing issues using a string project identifier."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        identifier = projects[0].identifier
        result = await list_redmine_issues(project_id=identifier)

        assert isinstance(result, list)
        # Should not return errors
        for issue in result:
            assert "error" not in issue

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_no_filters(self):
        """Test listing issues without any filters returns results."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues()

        assert isinstance(result, list)

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_with_limit(self):
        """Test that limit parameter caps the result count."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues(limit=3)

        assert isinstance(result, list)
        assert len(result) <= 3

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_pagination(self):
        """Test pagination with offset returns different results."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        page1 = await list_redmine_issues(limit=5, offset=0)
        page2 = await list_redmine_issues(limit=5, offset=5)

        assert isinstance(page1, list)
        assert isinstance(page2, list)

        # If both pages have results, their IDs should not overlap
        if page1 and page2:
            page1_ids = {issue["id"] for issue in page1 if "id" in issue}
            page2_ids = {issue["id"] for issue in page2 if "id" in issue}
            assert page1_ids.isdisjoint(page2_ids), "Pages should not overlap"

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_with_pagination_info(self):
        """Test include_pagination_info returns metadata."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues(
            limit=5, offset=0, include_pagination_info=True
        )

        assert isinstance(result, dict)
        assert "issues" in result
        assert "pagination" in result
        assert isinstance(result["issues"], list)

        pagination = result["pagination"]
        assert "total" in pagination
        assert "limit" in pagination
        assert "offset" in pagination
        assert "has_next" in pagination
        assert "has_previous" in pagination
        assert pagination["limit"] == 5
        assert pagination["offset"] == 0
        assert pagination["has_previous"] is False

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_with_status_filter(self):
        """Test filtering by status_id."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import (
            list_redmine_issues,
        )  # status_id=1 is typically "New" in Redmine

        result = await list_redmine_issues(status_id=1, limit=10)

        assert isinstance(result, list)
        for issue in result:
            if "error" not in issue:
                assert issue["status"]["id"] == 1

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_with_sort(self):
        """Test sorting issues by updated_on descending."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues(sort="updated_on:desc", limit=10)

        assert isinstance(result, list)
        # Verify ordering: each updated_on should be >= the next
        dates = [
            issue["updated_on"]
            for issue in result
            if "updated_on" in issue and issue["updated_on"]
        ]
        assert dates == sorted(dates, reverse=True)

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_field_selection(self):
        """Test field selection returns only requested fields."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues(limit=5, fields=["id", "subject", "status"])

        assert isinstance(result, list)
        for issue in result:
            if "error" not in issue:
                assert "id" in issue
                assert "subject" in issue
                assert "status" in issue
                assert "description" not in issue
                assert "author" not in issue

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_combined_project_and_status(self):
        """Test combining project_id and status_id filters."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].id
        # status_id=1 is typically "New"
        result = await list_redmine_issues(project_id=project_id, status_id=1, limit=10)

        assert isinstance(result, list)
        for issue in result:
            if "error" in issue:
                pytest.fail(f"API error: {issue['error']}")
            assert issue["project"]["id"] == project_id
            assert issue["status"]["id"] == 1

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_combined_filters_with_sort_and_pagination(self):
        """Test multiple filters combined with sort and pagination info."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].id
        result = await list_redmine_issues(
            project_id=project_id,
            status_id=1,
            sort="updated_on:desc",
            limit=5,
            offset=0,
            include_pagination_info=True,
        )

        assert isinstance(result, dict)
        assert "issues" in result
        assert "pagination" in result

        for issue in result["issues"]:
            assert issue["project"]["id"] == project_id
            assert issue["status"]["id"] == 1

        # Verify sort order
        dates = [
            issue["updated_on"] for issue in result["issues"] if issue.get("updated_on")
        ]
        assert dates == sorted(dates, reverse=True)

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_combined_filters_with_fields(self):
        """Test combined filters with field selection."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].id
        result = await list_redmine_issues(
            project_id=project_id,
            status_id=1,
            limit=5,
            fields=["id", "subject", "status", "project"],
        )

        assert isinstance(result, list)
        for issue in result:
            assert "id" in issue
            assert "subject" in issue
            assert "status" in issue
            assert "project" in issue
            assert "description" not in issue
            assert "author" not in issue
            assert issue["project"]["id"] == project_id
            assert issue["status"]["id"] == 1

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_issue_structure(self):
        """Test that returned issues have expected field structure."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues(limit=1)

        assert isinstance(result, list)
        if not result:
            pytest.skip("No issues found for testing")

        issue = result[0]
        assert "id" in issue
        assert "subject" in issue
        assert "project" in issue
        assert "status" in issue
        assert "priority" in issue
        assert "author" in issue

        assert isinstance(issue["id"], int)
        assert isinstance(issue["subject"], str)
        assert isinstance(issue["project"], dict)
        assert isinstance(issue["status"], dict)
        assert "id" in issue["project"]
        assert "name" in issue["project"]

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_issues_assigned_to_me(self):
        """Test filtering by assigned_to_id='me'."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import list_redmine_issues

        result = await list_redmine_issues(assigned_to_id="me", limit=10)

        assert isinstance(result, list)
        # Should not return errors
        for issue in result:
            assert "error" not in issue


@pytest.mark.integration
class TestEnvironmentConfiguration:
    """Test environment configuration and setup."""

    def test_environment_variables_loaded(self):
        """Test that environment variables are properly loaded."""
        from redmine_mcp_server._client import (  # noqa: E402
            REDMINE_URL,
            REDMINE_USERNAME,
            REDMINE_API_KEY,
        )

        if REDMINE_URL is None:
            pytest.skip("REDMINE_URL not configured")

        # At least REDMINE_URL should be set for the server to work
        assert REDMINE_URL is not None, "REDMINE_URL should be configured"

        # Either username or API key should be set
        has_username = REDMINE_USERNAME is not None
        has_api_key = REDMINE_API_KEY is not None

        assert (
            has_username or has_api_key
        ), "Either REDMINE_USERNAME or REDMINE_API_KEY should be configured"

    def test_redmine_client_initialization(self):
        """Test that Redmine client is properly initialized."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip(
                "Redmine client not initialized - check your .env configuration"
            )

        # Test that the client has expected attributes
        assert hasattr(redmine, "project")
        assert hasattr(redmine, "issue")


class TestListRedmineVersionsIntegration:
    """Integration tests for list_redmine_versions tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_versions_by_project_id(self):
        """Test listing versions for a project by numeric ID."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_versions

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].id
        result = await list_redmine_versions(project_id=project_id)

        assert isinstance(result, list)
        for version in result:
            if "error" in version:
                pytest.fail(f"API error: {version['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_versions_by_string_identifier(self):
        """Test listing versions using a string project identifier."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_versions

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        identifier = projects[0].identifier
        result = await list_redmine_versions(project_id=identifier)

        assert isinstance(result, list)
        for version in result:
            assert "error" not in version

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_versions_structure(self):
        """Test that returned version dicts have expected keys."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_versions

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        result = await list_redmine_versions(project_id=projects[0].id)

        assert isinstance(result, list)
        if not result:
            pytest.skip("No versions found in first project")

        version = result[0]
        expected_keys = {
            "id",
            "name",
            "description",
            "status",
            "due_date",
            "sharing",
            "wiki_page_title",
            "project",
            "created_on",
            "updated_on",
        }
        assert set(version.keys()) == expected_keys
        assert isinstance(version["id"], int)
        assert isinstance(version["name"], str)
        assert version["status"] in ("open", "locked", "closed")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_versions_filter_open(self):
        """Test filtering versions by open status."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_versions

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        result = await list_redmine_versions(
            project_id=projects[0].id, status_filter="open"
        )

        assert isinstance(result, list)
        for version in result:
            assert "error" not in version
            assert version["status"] == "open"

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_versions_invalid_status_filter(self):
        """Test that invalid status_filter returns error without API call."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_versions

        result = await list_redmine_versions(project_id=1, status_filter="invalid")

        assert isinstance(result, dict)
        assert "error" in result
        assert "invalid" in result["error"].lower()

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_versions_nonexistent_project(self):
        """Test error handling for a project that doesn't exist."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_redmine_versions

        result = await list_redmine_versions(project_id=999999)

        assert isinstance(result, dict)
        assert "error" in result


class TestListProjectMembersIntegration:
    """Integration tests for list_project_members tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_members_by_project_id(self):
        """Test listing members for a project by numeric ID."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_project_members

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        project_id = projects[0].id
        result = await list_project_members(project_id=project_id)

        assert isinstance(result, list)
        for member in result:
            if "error" in member:
                pytest.fail(f"API error: {member['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_members_by_string_identifier(self):
        """Test listing members using a string project identifier."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_project_members

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        identifier = projects[0].identifier
        result = await list_project_members(project_id=identifier)

        assert isinstance(result, list)
        for member in result:
            assert "error" not in member

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_members_structure(self):
        """Test that returned membership dicts have expected keys."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_project_members

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        result = await list_project_members(project_id=projects[0].id)

        assert isinstance(result, list)
        if not result:
            pytest.skip("No members found in first project")

        member = result[0]
        assert "id" in member
        assert "roles" in member
        assert isinstance(member["roles"], list)
        # Must have either user or group
        assert member["user"] is not None or member["group"] is not None

        if member["user"] is not None:
            assert "id" in member["user"]
            assert "name" in member["user"]

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_members_nonexistent_project(self):
        """Test error handling for a project that doesn't exist."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import list_project_members

        result = await list_project_members(project_id=999999)

        assert isinstance(result, dict)
        assert "error" in result


class TestTimeEntriesIntegration:
    """Integration tests for time entry tools."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entries_no_filters(self):
        """Test listing time entries without filters."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import list_time_entries

        result = await list_time_entries(limit=5)

        assert isinstance(result, list)
        for entry in result:
            if "error" in entry:
                if "denied" in entry["error"].lower():
                    pytest.skip(f"Time tracking not permitted: {entry['error']}")
                pytest.fail(f"API error: {entry['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entries_by_project(self):
        """Test filtering time entries by project."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import list_time_entries

        projects = list(redmine.project.all())
        if not projects:
            pytest.skip("No projects available for testing")

        result = await list_time_entries(project_id=projects[0].identifier, limit=5)

        assert isinstance(result, list)
        for entry in result:
            if "error" in entry:
                if "denied" in entry["error"].lower():
                    pytest.skip(f"Time tracking not permitted: {entry['error']}")
                pytest.fail(f"API error: {entry['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entries_by_current_user(self):
        """Test filtering time entries by current user."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import list_time_entries

        result = await list_time_entries(user_id="me", limit=5)

        assert isinstance(result, list)
        for entry in result:
            if "error" in entry:
                if "denied" in entry["error"].lower():
                    pytest.skip(f"Time tracking not permitted: {entry['error']}")
                pytest.fail(f"API error: {entry['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entries_structure(self):
        """Test that returned time entry dicts have expected keys."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import (  # noqa: E402
            list_time_entries,
            manage_time_entry,
        )

        # Ensure at least one time entry exists
        projects = list(redmine.project.all())
        assert projects, "No projects available"
        activity_id = _get_activity_id(redmine)
        assert activity_id is not None, "No time entry activities configured"

        created = await manage_time_entry(
            action="create",
            hours=0.1,
            project_id=projects[0].id,
            activity_id=activity_id,
            comments="Structure test entry",
        )
        assert "error" not in created, f"Failed to create: {created.get('error')}"

        try:
            result = await list_time_entries(limit=1)

            assert isinstance(result, list)
            assert len(result) > 0, "Expected at least one time entry"

            entry = result[0]
            if "error" in entry:
                pytest.fail(f"API error: {entry['error']}")
            assert "id" in entry
            assert "hours" in entry
            assert "comments" in entry
            assert "spent_on" in entry
            assert "user" in entry
            assert "project" in entry
            assert "activity" in entry
            assert isinstance(entry["id"], int)
            assert isinstance(entry["hours"], (int, float))
        finally:
            try:
                redmine.time_entry.delete(created["id"])
            except Exception:
                pass

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entries_pagination(self):
        """Test pagination with limit and offset."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import list_time_entries

        page1 = await list_time_entries(limit=3, offset=0)

        assert isinstance(page1, list)
        if page1 and "error" in page1[0]:
            if "denied" in page1[0]["error"].lower():
                pytest.skip(f"Time tracking not permitted: {page1[0]['error']}")

        page2 = await list_time_entries(limit=3, offset=3)

        assert isinstance(page2, list)
        assert len(page1) <= 3

        if page1 and page2:
            page1_ids = {e["id"] for e in page1 if "id" in e}
            page2_ids = {e["id"] for e in page2 if "id" in e}
            assert page1_ids.isdisjoint(page2_ids), "Pages should not overlap"

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_create_update_time_entry_lifecycle(self):
        """Integration test for creating and updating a time entry."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import (
            manage_time_entry,
        )  # Pick the first available project

        projects = list(redmine.project.all())
        assert projects, "No projects available for testing"
        project_id = projects[0].id

        # Find an activity_id (required by some Redmine configs)
        activity_id = _get_activity_id(redmine)
        assert activity_id is not None, "No time entry activities configured"

        time_entry_id = None
        try:
            # 1. Create a time entry
            create_result = await manage_time_entry(
                action="create",
                hours=0.25,
                project_id=project_id,
                activity_id=activity_id,
                comments="Integration test time entry",
            )

            if "error" in create_result:
                if (
                    "denied" in create_result["error"].lower()
                    or "forbidden" in create_result["error"].lower()
                ):
                    pytest.skip(
                        f"Time tracking not permitted: {create_result['error']}"
                    )
                pytest.fail(f"Failed to create time entry: {create_result['error']}")

            assert "id" in create_result
            assert create_result["hours"] == 0.25
            time_entry_id = create_result["id"]

            # 2. Update the time entry
            update_result = await manage_time_entry(
                action="update",
                time_entry_id=time_entry_id,
                hours=0.5,
                comments="Integration test time entry (updated)",
            )

            if "error" in update_result:
                pytest.fail(f"Failed to update time entry: {update_result['error']}")

            assert update_result["id"] == time_entry_id
            assert update_result["hours"] == 0.5

        except Exception as e:
            pytest.fail(f"Integration test failed: {e}")
        finally:
            # Clean up
            if time_entry_id is not None:
                try:
                    redmine.time_entry.delete(time_entry_id)
                except Exception:
                    pass  # Best effort cleanup

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_create_time_entry_validation(self):
        """Test that validation errors are returned correctly."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.time_tracking import (
            manage_time_entry,
        )  # Missing both project_id and issue_id

        result = await manage_time_entry(action="create", hours=1.0)
        assert "error" in result
        assert "project_id or issue_id" in result["error"]

        # Negative hours
        result = await manage_time_entry(action="create", hours=-1.0, project_id=1)
        assert "error" in result
        assert "positive" in result["error"]


class TestListProjectIssueCustomFieldsIntegration:
    """Integration tests for list_project_issue_custom_fields tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_custom_fields_for_project(self):
        """Test listing custom fields for an existing project."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import (
            list_project_issue_custom_fields,
        )  # noqa: E402

        projects = list(redmine.project.all())
        assert projects, "No projects available"

        result = await list_project_issue_custom_fields(
            project_id=projects[0].identifier
        )

        assert isinstance(result, list)
        if result and "error" in result[0]:
            pytest.fail(f"API error: {result[0]['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_custom_fields_structure(self):
        """Test that custom field dicts have expected keys."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import (
            list_project_issue_custom_fields,
        )  # noqa: E402

        projects = list(redmine.project.all())
        assert projects, "No projects available"

        result = await list_project_issue_custom_fields(
            project_id=projects[0].identifier
        )

        assert isinstance(result, list)
        if not result:
            # Project may have no custom fields; that's valid
            return

        if "error" in result[0]:
            pytest.fail(f"API error: {result[0]['error']}")

        field = result[0]
        assert "id" in field
        assert "name" in field

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_custom_fields_nonexistent_project(self):
        """Test error handling for nonexistent project."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import (
            list_project_issue_custom_fields,
        )  # noqa: E402

        result = await list_project_issue_custom_fields(
            project_id="nonexistent-project-xyz-99999"
        )

        assert isinstance(result, dict)
        assert "error" in result


class TestSearchRedmineIssuesIntegration:
    """Integration tests for search_redmine_issues tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_issues_basic(self):
        """Test basic issue search."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import search_redmine_issues

        result = await search_redmine_issues("test", limit=5)

        assert isinstance(result, list)
        for item in result:
            if "error" in item:
                pytest.fail(f"API error: {item['error']}")

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_issues_with_pagination(self):
        """Test search with pagination info."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import search_redmine_issues

        result = await search_redmine_issues(
            "test", limit=2, include_pagination_info=True
        )

        assert isinstance(result, dict)
        assert "issues" in result
        assert "pagination" in result
        assert isinstance(result["issues"], list)

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_issues_no_results(self):
        """Test search with a query that returns no results."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.issues import search_redmine_issues

        result = await search_redmine_issues("zzz_nonexistent_xyzzy_999", limit=5)

        assert isinstance(result, list)
        assert len(result) == 0


class TestSummarizeProjectStatusIntegration:
    """Integration tests for summarize_project_status tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_summarize_project_basic(self):
        """Test basic project status summary."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import summarize_project_status

        projects = list(redmine.project.all())
        assert projects, "No projects available"

        result = await summarize_project_status(project_id=projects[0].id, days=30)

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert "project" in result
        assert "analysis_period" in result

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_summarize_project_structure(self):
        """Test that summary has expected structure."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import summarize_project_status

        projects = list(redmine.project.all())
        assert projects, "No projects available"

        result = await summarize_project_status(project_id=projects[0].id, days=7)

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert "project" in result
        assert "analysis_period" in result
        assert "project_totals" in result
        assert "recent_activity" in result
        assert "insights" in result
        period = result["analysis_period"]
        assert "days" in period
        assert "start_date" in period
        assert "end_date" in period
        assert "total_issues" in result["project_totals"]

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_summarize_nonexistent_project(self):
        """Test error handling for nonexistent project."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.projects import summarize_project_status

        result = await summarize_project_status(project_id=999999, days=30)

        assert isinstance(result, dict)
        assert "error" in result


class TestSearchEntireRedmineIntegration:
    """Integration tests for search_entire_redmine tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_entire_basic(self):
        """Test basic cross-resource search."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.search import search_entire_redmine

        result = await search_entire_redmine(query="test", limit=5)

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert "results" in result
        assert "total_count" in result

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_entire_filter_issues(self):
        """Test searching only issues."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.search import search_entire_redmine

        result = await search_entire_redmine(
            query="test", resources=["issues"], limit=5
        )

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert "results" in result

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_entire_filter_wiki(self):
        """Test searching only wiki pages."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.search import search_entire_redmine

        result = await search_entire_redmine(
            query="test", resources=["wiki_pages"], limit=5
        )

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert "results" in result

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_search_entire_no_results(self):
        """Test search that returns no results."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.search import search_entire_redmine

        result = await search_entire_redmine(query="zzz_nonexistent_xyzzy_999", limit=5)

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert result["total_count"] == 0


class TestCleanupAttachmentFilesIntegration:
    """Integration tests for cleanup_attachment_files tool."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_cleanup_basic(self):
        """Test cleanup returns expected structure."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.files import cleanup_attachment_files

        result = await cleanup_attachment_files()

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        assert "cleanup" in result
        assert "current_storage" in result

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_cleanup_structure(self):
        """Test cleanup stats have expected keys."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        from redmine_mcp_server.tools.files import cleanup_attachment_files

        result = await cleanup_attachment_files()

        assert isinstance(result, dict)
        if "error" in result:
            pytest.fail(f"API error: {result['error']}")

        cleanup = result["cleanup"]
        assert "cleaned_files" in cleanup
        assert isinstance(cleanup["cleaned_files"], int)

        storage = result["current_storage"]
        assert "total_files" in storage or "file_count" in storage


class TestEnumerationsIntegration:
    """Integration tests for enumeration/lookup tools."""

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entry_activities(self):
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        result = await list_time_entry_activities()
        assert isinstance(result, list)
        assert len(result) > 0, "Should have at least one activity"

    @pytest.mark.skipif(not REDMINE_URL, reason="REDMINE_URL not configured")
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_list_time_entry_activities_structure(self):
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        result = await list_time_entry_activities()
        assert len(result) > 0
        activity = result[0]
        assert "id" in activity
        assert "name" in activity
        assert "active" in activity
        assert "is_default" in activity
        assert isinstance(activity["id"], int)
        assert isinstance(activity["name"], str)


_AGILE_SKIP = pytest.mark.skipif(
    not REDMINE_URL
    or os.getenv("REDMINE_AGILE_ENABLED", "false").strip().lower()
    not in {"1", "true", "yes", "on"},
    reason="REDMINE_URL not configured or REDMINE_AGILE_ENABLED not true",
)


class TestAgilePluginIntegration:
    """Integration tests for RedmineUP Agile plugin support.

    Requires:
    - REDMINE_URL configured
    - REDMINE_AGILE_ENABLED=true
    - RedmineUP Agile plugin installed on the Redmine server
    - The 'agile' module enabled for the test project (Project Settings → Modules)
    """

    def _find_agile_issue_id(self, redmine):
        """Find an issue in a project with the agile module enabled."""
        project_id = os.getenv("REDMINE_AGILE_TEST_PROJECT_ID")
        if project_id:
            try:
                issues = redmine.issue.filter(project_id=project_id, limit=1)
                if issues:
                    return list(issues)[0].id
            except Exception:
                pass
            pytest.skip(
                f"No issues found in REDMINE_AGILE_TEST_PROJECT_ID={project_id}"
            )

        # Fall back: try each project until one returns agile data without 403
        try:
            projects = list(redmine.project.all())
        except Exception:
            pytest.skip("Could not list projects")

        for project in projects:
            try:
                issues = redmine.issue.filter(project_id=project.id, limit=1)
                issue_list = list(issues)
                if not issue_list:
                    continue
                issue_id = issue_list[0].id
                # Probe agile endpoint — skip project on 403
                from redminelib.exceptions import ForbiddenError

                try:
                    url = f"{REDMINE_URL}/issues/{issue_id}/agile_data.json"
                    redmine.engine.request("get", url)
                    return issue_id
                except ForbiddenError:
                    continue
            except Exception:
                continue

        pytest.skip(
            "No project with Agile module enabled found. "
            "Enable the 'agile' module in at least one project."
        )

    @_AGILE_SKIP
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_get_issue_includes_agile_fields(self):
        """get_redmine_issue returns agile fields when REDMINE_AGILE_ENABLED=true."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        issue_id = self._find_agile_issue_id(redmine)

        from redmine_mcp_server.tools.issues import get_redmine_issue

        result = await get_redmine_issue(issue_id)

        assert "error" not in result, f"get_redmine_issue failed: {result}"
        assert "story_points" in result, "story_points missing from result"
        assert "agile_sprint_id" in result, "agile_sprint_id missing from result"
        assert "agile_position" in result, "agile_position missing from result"
        # Values may be None (not yet set) — presence of keys is what matters
        assert result.get("story_points") is None or isinstance(
            result["story_points"], (int, float)
        )

    @_AGILE_SKIP
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_update_story_points_roundtrip(self):
        """update_redmine_issue sets story_points and get_redmine_issue reads it back."""  # noqa: E501
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        issue_id = self._find_agile_issue_id(redmine)

        from redmine_mcp_server.tools.issues import (  # noqa: E402
            get_redmine_issue,
            update_redmine_issue,
        )

        # Set story points to a known value
        update_result = await update_redmine_issue(issue_id, {"story_points": 5})
        assert "error" not in update_result, f"update failed: {update_result}"

        # Read back and confirm
        get_result = await get_redmine_issue(issue_id)
        assert "error" not in get_result, f"get failed: {get_result}"
        assert get_result.get("story_points") == 5

    @_AGILE_SKIP
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_clear_story_points(self):
        """update_redmine_issue with story_points=None clears the field."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        issue_id = self._find_agile_issue_id(redmine)

        from redmine_mcp_server.tools.issues import (  # noqa: E402
            get_redmine_issue,
            update_redmine_issue,
        )

        # First set a value so there's something to clear
        await update_redmine_issue(issue_id, {"story_points": 3})

        # Now clear it
        update_result = await update_redmine_issue(issue_id, {"story_points": None})
        assert "error" not in update_result, f"clear failed: {update_result}"

        # Read back and confirm cleared
        get_result = await get_redmine_issue(issue_id)
        assert "error" not in get_result
        assert get_result.get("story_points") is None

    @_AGILE_SKIP
    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_story_points_not_leaked_to_standard_update(self):
        """story_points in fields dict does not cause a standard update failure."""
        redmine = _get_redmine_or_none()
        if redmine is None:
            pytest.skip("Redmine client not initialized")

        issue_id = self._find_agile_issue_id(redmine)

        from redmine_mcp_server.tools.issues import (
            update_redmine_issue,
        )  # Passing story_points together with a standard field must not error

        result = await update_redmine_issue(
            issue_id, {"story_points": 8, "notes": "agile integration test"}
        )
        assert (
            "error" not in result
        ), f"Combined story_points + notes update failed: {result}"


if __name__ == "__main__":
    # Run integration tests
    pytest.main([__file__, "-v", "-m", "integration", "--tb=short"])
