"""Mock AI provider for testing purposes only.

This mock implementation is only used during tests, following the mock
placement rule that all mocks must exist in the features/ directory,
never in production code (ADR-022).
"""

import os
from collections.abc import Callable, Iterator

from cleveragents.domain.models.core import (
    Change,
    Context,
    OperationType,
    Plan,
    Project,
)
from cleveragents.domain.providers.ai_provider import (
    ActorInvocationContext,
    ProviderResponse,
)


class MockAIProvider:
    """Mock AI provider that generates simple test responses.

    This provider is used only during testing to avoid calling real AI APIs.
    """

    def __init__(self, model_id: str = "mock-gpt-4"):
        """Initialize the mock provider.

        Args:
            model_id: Mock model identifier
        """
        self._model_id = model_id
        self._name = "MockProvider"

    def generate_changes(
        self,
        project: Project,
        plan: Plan,
        contexts: list[Context],
        actor_context: ActorInvocationContext | None = None,
        progress_callback: Callable[[int], None] | None = None,
    ) -> ProviderResponse:
        """Generate mock code changes based on the plan.

        Args:
            project: The project being modified
            plan: The plan containing instructions
            contexts: List of context files to consider
            progress_callback: Optional callback for progress updates (0-100)

        Returns:
            ProviderResponse containing mock changes
        """
        # Check if provider should fail (for testing error handling)
        if os.environ.get("CLEVERAGENTS_MOCK_SHOULD_FAIL") == "true":
            return ProviderResponse(
                changes=[],
                model_used=self._model_id,
                token_count=0,
                error_message="Mock provider configured to fail for testing",
            )

        # Simulate progress
        if progress_callback:
            for i in range(0, 101, 20):
                progress_callback(i)

        # Generate simple mock changes based on the prompt
        prompt_lower = plan.prompt.lower() if plan.prompt else ""

        # Check if provider should generate invalid code (for testing validation)
        if os.environ.get("CLEVERAGENTS_MOCK_INVALID_CODE") == "true":
            changes = [
                Change(
                    id=None,
                    plan_id=plan.id if plan.id else 0,
                    file_path="invalid.py",
                    operation=OperationType.CREATE,
                    original_content=None,
                    new_content=(
                        "# Invalid Python code\n"
                        "def broken_function(\n"
                        "    # Missing closing parenthesis and colon\n"
                        "    # This will fail syntax validation\n"
                    ),
                    applied=False,
                    applied_at=None,
                    new_path=None,
                )
            ]
            return ProviderResponse(
                changes=changes,
                model_used=self._model_id,
                token_count=50,
                error_message=None,
            )

        if not plan.id:
            # Create temporary changes without plan_id for tests
            changes = [
                Change(
                    id=None,
                    plan_id=0,  # Use 0 as placeholder
                    file_path="example.py",
                    operation=OperationType.CREATE,
                    original_content=None,
                    new_content="# Mock AI generated code\nprint('Hello from CleverAgents!')\n",
                    applied=False,
                    applied_at=None,
                    new_path=None,
                )
            ]
        else:
            # Determine what kind of change to generate based on prompt
            if "error handling" in prompt_lower or "exception" in prompt_lower:
                changes = [
                    Change(
                        id=None,
                        plan_id=plan.id,
                        file_path="error_handler.py",
                        operation=OperationType.CREATE,
                        original_content=None,
                        new_content="""# Mock error handling code
def handle_error(error):
    \"\"\"Mock error handler.\"\"\"
    print(f"Error occurred: {error}")
    return None
""",
                        applied=False,
                        applied_at=None,
                        new_path=None,
                    )
                ]
            elif "test" in prompt_lower:
                changes = [
                    Change(
                        id=None,
                        plan_id=plan.id,
                        file_path="test_example.py",
                        operation=OperationType.CREATE,
                        original_content=None,
                        new_content="""# Mock test file
import pytest

def test_example():
    \"\"\"Mock test.\"\"\"
    assert True
""",
                        applied=False,
                        applied_at=None,
                        new_path=None,
                    )
                ]
            elif "refactor" in prompt_lower or "improve" in prompt_lower:
                # Create a modification change if context files exist
                if contexts and len(contexts) > 0:
                    file_path = contexts[0].path
                    changes = [
                        Change(
                            id=None,
                            plan_id=plan.id,
                            file_path=file_path,
                            operation=OperationType.MODIFY,
                            original_content=contexts[0].content,
                            new_content=(
                                f"# Refactored by mock AI\n{contexts[0].content}"
                            ),
                            applied=False,
                            applied_at=None,
                            new_path=None,
                        )
                    ]
                else:
                    # Default to creating a new file
                    changes = [
                        Change(
                            id=None,
                            plan_id=plan.id,
                            file_path="refactored.py",
                            operation=OperationType.CREATE,
                            original_content=None,
                            new_content=(
                                "# Mock refactored code\n"
                                "class RefactoredClass:\n"
                                "    pass\n"
                            ),
                            applied=False,
                            applied_at=None,
                            new_path=None,
                        )
                    ]
            else:
                # Default change
                changes = [
                    Change(
                        id=None,
                        plan_id=plan.id,
                        file_path="example.py",
                        operation=OperationType.CREATE,
                        original_content=None,
                        new_content=(
                            "# Mock AI generated code\n"
                            "print('Hello from CleverAgents!')\n"
                        ),
                        applied=False,
                        applied_at=None,
                        new_path=None,
                    )
                ]

        return ProviderResponse(
            changes=changes,
            model_used=self._model_id,
            token_count=100,
            error_message=None,
        )

    def stream_changes(
        self,
        project: Project,
        plan: Plan,
        contexts: list[Context],
        actor_context: ActorInvocationContext | None = None,
        progress_callback: Callable[[int], None] | None = None,
    ) -> Iterator[dict[str, object]]:
        """Stream mock workflow events for CLI coverage."""

        def _stream() -> Iterator[dict[str, object]]:
            if progress_callback:
                progress_callback(5)
            response = self.generate_changes(
                project,
                plan,
                contexts,
                progress_callback=None,
            )
            for node in (
                "load_context",
                "analyze_requirements",
                "generate_plan",
                "validate",
            ):
                event_payload: dict[str, object] = {}
                if node == "generate_plan":
                    event_payload = {
                        "status": "completed",
                        "change_count": len(response.changes),
                    }
                elif node == "validate":
                    event_payload = {"status": "completed"}
                yield {node: event_payload}
            yield {"__end__": {"response": response}}

        return _stream()

    @property
    def name(self) -> str:
        """Get the provider name."""
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        """Allow overriding the provider name in tests."""
        self._name = value

    @property
    def model_id(self) -> str:
        """Get the model identifier."""
        return self._model_id

    @model_id.setter
    def model_id(self, value: str) -> None:
        """Allow overriding the model identifier in tests."""
        self._model_id = value
