import json
from unittest.mock import patch

import pytest
from faker import Faker
from pydantic import ValidationError
from sqlalchemy.orm import Session

from core.tools.entities.tool_entities import WorkflowToolParameterConfiguration
from core.tools.errors import WorkflowToolHumanInputNotSupportedError
from models.tools import WorkflowToolProvider
from models.workflow import Workflow as WorkflowModel
from services.account_service import AccountService, TenantService
from services.app_service import AppService, CreateAppParams
from services.tools.workflow_tools_manage_service import WorkflowToolManageService
from tests.test_containers_integration_tests.helpers import generate_valid_password


class TestWorkflowToolManageService:
    """Integration tests for WorkflowToolManageService using testcontainers."""

    @pytest.fixture
    def mock_external_service_dependencies(self):
        """Mock setup for external service dependencies."""
        with (
            patch("services.app_service.FeatureService") as mock_feature_service,
            patch("services.app_service.EnterpriseService") as mock_enterprise_service,
            patch("services.app_service.ModelManager.for_tenant") as mock_model_manager,
            patch("services.account_service.FeatureService") as mock_account_feature_service,
            patch(
                "services.tools.workflow_tools_manage_service.WorkflowToolProviderController"
            ) as mock_workflow_tool_provider_controller,
            patch("services.tools.workflow_tools_manage_service.ToolLabelManager") as mock_tool_label_manager,
            patch("services.tools.workflow_tools_manage_service.ToolTransformService") as mock_tool_transform_service,
        ):
            # Setup default mock returns for app service
            mock_feature_service.get_system_features.return_value.webapp_auth.enabled = False
            mock_enterprise_service.WebAppAuth.update_app_access_mode.return_value = None
            mock_enterprise_service.WebAppAuth.cleanup_webapp.return_value = None

            # Setup default mock returns for account service
            mock_account_feature_service.get_system_features.return_value.is_allow_register = True

            # Mock ModelManager for model configuration
            mock_model_instance = mock_model_manager.return_value
            mock_model_instance.get_default_model_instance.return_value = None
            mock_model_instance.get_default_provider_model_name.return_value = ("openai", "gpt-3.5-turbo")

            # Mock WorkflowToolProviderController
            mock_workflow_tool_provider_controller.from_db.return_value = None

            # Mock ToolLabelManager
            mock_tool_label_manager.update_tool_labels.return_value = None

            # Mock ToolTransformService
            mock_tool_transform_service.workflow_provider_to_controller.return_value = None

            yield {
                "feature_service": mock_feature_service,
                "enterprise_service": mock_enterprise_service,
                "model_manager": mock_model_manager,
                "account_feature_service": mock_account_feature_service,
                "workflow_tool_provider_controller": mock_workflow_tool_provider_controller,
                "tool_label_manager": mock_tool_label_manager,
                "tool_transform_service": mock_tool_transform_service,
            }

    def _create_test_app_and_account(self, db_session_with_containers: Session, mock_external_service_dependencies):
        """
        Helper method to create a test app and account for testing.

        Args:
            db_session_with_containers: Database session from testcontainers infrastructure
            mock_external_service_dependencies: Mock dependencies

        Returns:
            tuple: (app, account, workflow) - Created app, account and workflow instances
        """
        fake = Faker()

        # Setup mocks for account creation
        mock_external_service_dependencies[
            "account_feature_service"
        ].get_system_features.return_value.is_allow_register = True

        # Create account and tenant
        account = AccountService.create_account(
            email=fake.email(),
            name=fake.name(),
            interface_language="en-US",
            password=generate_valid_password(fake),
        )
        TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
        tenant = account.current_tenant

        # Create app with realistic data
        app_args = CreateAppParams(
            name=fake.company(),
            description=fake.text(max_nb_chars=100),
            mode="workflow",
            icon_type="emoji",
            icon="🤖",
            icon_background="#FF6B6B",
            api_rph=100,
            api_rpm=10,
        )

        app_service = AppService()
        app = app_service.create_app(tenant.id, app_args, account)

        # Create workflow for the app
        workflow = WorkflowModel(
            tenant_id=tenant.id,
            app_id=app.id,
            type="workflow",
            version="1.0.0",
            graph=json.dumps({}),
            features=json.dumps({}),
            created_by=account.id,
            environment_variables=[],
            conversation_variables=[],
        )

        db_session_with_containers.add(workflow)
        db_session_with_containers.commit()

        # Update app to reference the workflow
        app.workflow_id = workflow.id
        db_session_with_containers.commit()

        return app, account, workflow

    def _create_test_workflow_tool_parameters(self):
        """Helper method to create valid workflow tool parameters."""
        return [
            WorkflowToolParameterConfiguration.model_validate(
                {
                    "name": "input_text",
                    "description": "Input text for processing",
                    "form": "form",
                    "type": "string",
                    "required": True,
                }
            ),
            WorkflowToolParameterConfiguration.model_validate(
                {
                    "name": "output_format",
                    "description": "Output format specification",
                    "form": "form",
                    "type": "select",
                    "required": False,
                }
            ),
        ]

    def test_create_workflow_tool_success(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test successful workflow tool creation with valid parameters.

        This test verifies:
        - Proper workflow tool creation with all required fields
        - Correct database state after creation
        - Proper relationship establishment
        - External service integration
        - Return value correctness
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Setup workflow tool creation parameters
        tool_name = fake.word()
        tool_label = fake.word()
        tool_icon = {"type": "emoji", "emoji": "🔧"}
        tool_description = fake.text(max_nb_chars=200)
        tool_parameters = self._create_test_workflow_tool_parameters()
        tool_privacy_policy = fake.text(max_nb_chars=100)
        tool_labels = ["automation", "workflow"]

        # Execute the method under test
        result = WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=tool_name,
            label=tool_label,
            icon=tool_icon,
            description=tool_description,
            parameters=tool_parameters,
            privacy_policy=tool_privacy_policy,
            labels=tool_labels,
        )

        # Verify the result
        assert result == {"result": "success"}

        # Verify database state

        # Check if workflow tool provider was created
        created_tool_provider = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
                WorkflowToolProvider.app_id == app.id,
            )
            .first()
        )

        assert created_tool_provider is not None
        assert created_tool_provider.name == tool_name
        assert created_tool_provider.label == tool_label
        assert created_tool_provider.icon == json.dumps(tool_icon)
        assert created_tool_provider.description == tool_description
        assert created_tool_provider.parameter_configuration == json.dumps([p.model_dump() for p in tool_parameters])
        assert created_tool_provider.privacy_policy == tool_privacy_policy
        assert created_tool_provider.version == workflow.version
        assert created_tool_provider.user_id == account.id
        assert created_tool_provider.tenant_id == account.current_tenant.id
        assert created_tool_provider.app_id == app.id

        # Verify external service calls
        mock_external_service_dependencies["workflow_tool_provider_controller"].from_db.assert_called_once()
        mock_external_service_dependencies["tool_label_manager"].update_tool_labels.assert_called_once()
        mock_external_service_dependencies[
            "tool_transform_service"
        ].workflow_provider_to_controller.assert_called_once()

    def test_create_workflow_tool_duplicate_name_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation fails when name already exists.

        This test verifies:
        - Proper error handling for duplicate tool names
        - Database constraint enforcement
        - Correct error message
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create first workflow tool
        first_tool_name = fake.word()
        first_tool_parameters = self._create_test_workflow_tool_parameters()

        WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=first_tool_name,
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=first_tool_parameters,
        )

        # Attempt to create second workflow tool with same name
        second_tool_parameters = self._create_test_workflow_tool_parameters()
        with pytest.raises(ValueError) as exc_info:
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=app.id,
                name=first_tool_name,  # Same name
                label=fake.word(),
                icon={"type": "emoji", "emoji": "⚙️"},
                description=fake.text(max_nb_chars=200),
                parameters=second_tool_parameters,
            )

        # Verify error message
        assert f"Tool with name {first_tool_name} or app_id {app.id} already exists" in str(exc_info.value)

        # Verify only one tool was created

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 1

    def test_create_workflow_tool_invalid_app_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation fails when app does not exist.

        This test verifies:
        - Proper error handling for non-existent apps
        - Correct error message
        - No database changes when app is invalid
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Generate non-existent app ID
        non_existent_app_id = fake.uuid4()

        # Attempt to create workflow tool with non-existent app
        tool_parameters = self._create_test_workflow_tool_parameters()
        with pytest.raises(ValueError) as exc_info:
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=non_existent_app_id,  # Non-existent app ID
                name=fake.word(),
                label=fake.word(),
                icon={"type": "emoji", "emoji": "🔧"},
                description=fake.text(max_nb_chars=200),
                parameters=tool_parameters,
            )

        # Verify error message
        assert f"App {non_existent_app_id} not found" in str(exc_info.value)

        # Verify no workflow tool was created

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 0

    def test_create_workflow_tool_invalid_parameters_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation fails when parameters are invalid.

        This test verifies:
        - Proper error handling for invalid parameter configurations
        - Parameter validation enforcement
        - Correct error message
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )
        # Attempt to create workflow tool with invalid parameters
        with pytest.raises(ValidationError) as exc_info:
            # Setup invalid workflow tool parameters (missing required fields)
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=app.id,
                name=fake.word(),
                label=fake.word(),
                icon={"type": "emoji", "emoji": "🔧"},
                description=fake.text(max_nb_chars=200),
                parameters=[
                    WorkflowToolParameterConfiguration.model_validate(
                        {
                            "name": "input_text",
                            # Missing description and form fields
                            "type": "string",
                            "required": True,
                        }
                    )
                ],
            )

        # Verify error message contains validation error
        assert "validation error" in str(exc_info.value).lower()

        # Verify no workflow tool was created

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 0

    def test_create_workflow_tool_duplicate_app_id_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation fails when app_id already exists.

        This test verifies:
        - Proper error handling for duplicate app_id
        - Database constraint enforcement for app_id uniqueness
        - Correct error message
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create first workflow tool
        first_tool_name = fake.word()
        first_tool_parameters = self._create_test_workflow_tool_parameters()
        WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=first_tool_name,
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=first_tool_parameters,
        )

        # Attempt to create second workflow tool with same app_id but different name
        second_tool_name = fake.word()
        second_tool_parameters = self._create_test_workflow_tool_parameters()
        with pytest.raises(ValueError) as exc_info:
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=app.id,  # Same app_id
                name=second_tool_name,  # Different name
                label=fake.word(),
                icon={"type": "emoji", "emoji": "⚙️"},
                description=fake.text(max_nb_chars=200),
                parameters=second_tool_parameters,
            )

        # Verify error message
        assert f"Tool with name {second_tool_name} or app_id {app.id} already exists" in str(exc_info.value)

        # Verify only one tool was created

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 1

    def test_create_workflow_tool_workflow_not_found_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation fails when app has no workflow.

        This test verifies:
        - Proper error handling for apps without workflows
        - Correct error message
        - No database changes when workflow is missing
        """
        fake = Faker()

        # Create test data but without workflow
        app, account, _ = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Remove workflow reference from app

        app.workflow_id = None
        db_session_with_containers.commit()

        # Attempt to create workflow tool for app without workflow
        tool_parameters = self._create_test_workflow_tool_parameters()
        with pytest.raises(ValueError) as exc_info:
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=app.id,
                name=fake.word(),
                label=fake.word(),
                icon={"type": "emoji", "emoji": "🔧"},
                description=fake.text(max_nb_chars=200),
                parameters=tool_parameters,
            )

        # Verify error message
        assert f"Workflow not found for app {app.id}" in str(exc_info.value)

        # Verify no workflow tool was created
        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 0

    def test_create_workflow_tool_human_input_node_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation fails when workflow contains human input nodes.

        This test verifies:
        - Human input nodes prevent workflow tool publishing
        - Correct error message
        - No database changes when workflow is invalid
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        workflow.graph = json.dumps(
            {
                "nodes": [
                    {
                        "id": "human_input_node",
                        "data": {"type": "human-input"},
                    }
                ]
            }
        )

        tool_parameters = self._create_test_workflow_tool_parameters()
        with pytest.raises(WorkflowToolHumanInputNotSupportedError) as exc_info:
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=app.id,
                name=fake.word(),
                label=fake.word(),
                icon={"type": "emoji", "emoji": "🔧"},
                description=fake.text(max_nb_chars=200),
                parameters=tool_parameters,
            )

        assert exc_info.value.error_code == "workflow_tool_human_input_not_supported"

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 0

    def test_update_workflow_tool_success(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test successful workflow tool update with valid parameters.

        This test verifies:
        - Proper workflow tool update with all required fields
        - Correct database state after update
        - Proper relationship maintenance
        - External service integration
        - Return value correctness
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create initial workflow tool
        initial_tool_name = fake.word()
        initial_tool_parameters = self._create_test_workflow_tool_parameters()
        WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=initial_tool_name,
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=initial_tool_parameters,
        )

        # Get the created tool

        created_tool = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
                WorkflowToolProvider.app_id == app.id,
            )
            .first()
        )

        # Setup update parameters
        updated_tool_name = fake.word()
        updated_tool_label = fake.word()
        updated_tool_icon = {"type": "emoji", "emoji": "⚙️"}
        updated_tool_description = fake.text(max_nb_chars=200)
        updated_tool_parameters = self._create_test_workflow_tool_parameters()
        updated_tool_privacy_policy = fake.text(max_nb_chars=100)
        updated_tool_labels = ["automation", "updated"]

        # Execute the update method
        result = WorkflowToolManageService.update_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_tool_id=created_tool.id,
            name=updated_tool_name,
            label=updated_tool_label,
            icon=updated_tool_icon,
            description=updated_tool_description,
            parameters=updated_tool_parameters,
            privacy_policy=updated_tool_privacy_policy,
            labels=updated_tool_labels,
        )

        # Verify the result
        assert result == {"result": "success"}

        # Verify database state was updated
        db_session_with_containers.refresh(created_tool)
        assert created_tool is not None
        assert created_tool.name == updated_tool_name
        assert created_tool.label == updated_tool_label
        assert created_tool.icon == json.dumps(updated_tool_icon)
        assert created_tool.description == updated_tool_description
        assert created_tool.parameter_configuration == json.dumps([p.model_dump() for p in updated_tool_parameters])
        assert created_tool.privacy_policy == updated_tool_privacy_policy
        assert created_tool.version == workflow.version
        assert created_tool.updated_at is not None

        # Verify external service calls
        mock_external_service_dependencies["workflow_tool_provider_controller"].from_db.assert_called()
        mock_external_service_dependencies["tool_label_manager"].update_tool_labels.assert_called()
        mock_external_service_dependencies["tool_transform_service"].workflow_provider_to_controller.assert_called()

    def test_update_workflow_tool_human_input_node_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool update fails when workflow contains human input nodes.

        This test verifies:
        - Human input nodes prevent workflow tool updates
        - Correct error message
        - Existing tool data remains unchanged
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create initial workflow tool
        initial_tool_name = fake.word()
        initial_tool_parameters = self._create_test_workflow_tool_parameters()
        WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=initial_tool_name,
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=initial_tool_parameters,
        )

        created_tool = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
                WorkflowToolProvider.app_id == app.id,
            )
            .first()
        )

        original_name = created_tool.name

        workflow.graph = json.dumps(
            {
                "nodes": [
                    {
                        "id": "human_input_node",
                        "data": {"type": "human-input"},
                    }
                ]
            }
        )
        db_session_with_containers.commit()

        with pytest.raises(WorkflowToolHumanInputNotSupportedError) as exc_info:
            WorkflowToolManageService.update_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_tool_id=created_tool.id,
                name=fake.word(),
                label=fake.word(),
                icon={"type": "emoji", "emoji": "⚙️"},
                description=fake.text(max_nb_chars=200),
                parameters=initial_tool_parameters,
            )

        assert exc_info.value.error_code == "workflow_tool_human_input_not_supported"

        db_session_with_containers.refresh(created_tool)
        assert created_tool.name == original_name

    def test_update_workflow_tool_not_found_error(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool update fails when tool does not exist.

        This test verifies:
        - Proper error handling for non-existent tools
        - Correct error message
        - No database changes when tool is invalid
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Generate non-existent tool ID
        non_existent_tool_id = fake.uuid4()

        # Attempt to update non-existent workflow tool
        tool_parameters = self._create_test_workflow_tool_parameters()
        with pytest.raises(ValueError) as exc_info:
            WorkflowToolManageService.update_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_tool_id=non_existent_tool_id,  # Non-existent tool ID
                name=fake.word(),
                label=fake.word(),
                icon={"type": "emoji", "emoji": "🔧"},
                description=fake.text(max_nb_chars=200),
                parameters=tool_parameters,
            )

        # Verify error message
        assert f"Tool {non_existent_tool_id} not found" in str(exc_info.value)

        # Verify no workflow tool was created

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
            )
            .count()
        )

        assert tool_count == 0

    def test_update_workflow_tool_same_name_success(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool update succeeds when keeping the same name.

        This test verifies:
        - Proper handling when updating tool with same name
        - Database state maintenance
        - Update timestamp is set
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create first workflow tool
        first_tool_name = fake.word()
        first_tool_parameters = self._create_test_workflow_tool_parameters()
        WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=first_tool_name,
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=first_tool_parameters,
        )

        # Get the created tool

        created_tool = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
                WorkflowToolProvider.app_id == app.id,
            )
            .first()
        )

        # Attempt to update tool with same name (should not fail)
        result = WorkflowToolManageService.update_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_tool_id=created_tool.id,
            name=first_tool_name,  # Same name
            label=fake.word(),
            icon={"type": "emoji", "emoji": "⚙️"},
            description=fake.text(max_nb_chars=200),
            parameters=first_tool_parameters,
        )

        # Verify update was successful
        assert result == {"result": "success"}

        # Verify tool still exists with the same name
        db_session_with_containers.refresh(created_tool)
        assert created_tool.name == first_tool_name
        assert created_tool.updated_at is not None

    def test_create_workflow_tool_with_file_parameter_default(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation with FILE parameter having a file object as default.

        This test verifies:
        - FILE parameters can have file object defaults
        - The default value (dict with id/base64Url) is properly handled
        - Tool creation succeeds without Pydantic validation errors

        Related issue: Array[File] default value causes Pydantic validation errors.
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create workflow graph with a FILE variable that has a default value
        workflow_graph = {
            "nodes": [
                {
                    "id": "start_node",
                    "data": {
                        "type": "start",
                        "variables": [
                            {
                                "variable": "document",
                                "label": "Document",
                                "type": "file",
                                "required": False,
                                "default": {"id": fake.uuid4(), "base64Url": ""},
                            }
                        ],
                    },
                }
            ]
        }
        workflow.graph = json.dumps(workflow_graph)

        # Setup workflow tool parameters with FILE type
        file_parameters = [
            WorkflowToolParameterConfiguration.model_validate(
                {
                    "name": "document",
                    "description": "Upload a document",
                    "form": "form",
                    "type": "file",
                    "required": False,
                }
            )
        ]

        # Execute the method under test
        # Note: from_db is mocked, so this test primarily validates the parameter configuration
        result = WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=fake.word(),
            label=fake.word(),
            icon={"type": "emoji", "emoji": "📄"},
            description=fake.text(max_nb_chars=200),
            parameters=file_parameters,
        )

        # Verify the result
        assert result == {"result": "success"}

    def test_create_workflow_tool_with_files_parameter_default(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test workflow tool creation with FILES (Array[File]) parameter having file objects as default.

        This test verifies:
        - FILES parameters can have a list of file objects as default
        - The default value (list of dicts with id/base64Url) is properly handled
        - Tool creation succeeds without Pydantic validation errors

        Related issue: Array[File] default value causes 4 Pydantic validation errors
        because PluginParameter.default only accepts Union[float, int, str, bool] | None.
        """
        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        # Create workflow graph with a FILE_LIST variable that has a default value
        workflow_graph = {
            "nodes": [
                {
                    "id": "start_node",
                    "data": {
                        "type": "start",
                        "variables": [
                            {
                                "variable": "documents",
                                "label": "Documents",
                                "type": "file-list",
                                "required": False,
                                "default": [
                                    {"id": fake.uuid4(), "base64Url": ""},
                                    {"id": fake.uuid4(), "base64Url": ""},
                                ],
                            }
                        ],
                    },
                }
            ]
        }
        workflow.graph = json.dumps(workflow_graph)

        # Setup workflow tool parameters with FILES type
        files_parameters = [
            WorkflowToolParameterConfiguration.model_validate(
                {
                    "name": "documents",
                    "description": "Upload multiple documents",
                    "form": "form",
                    "type": "files",
                    "required": False,
                }
            )
        ]

        # Execute the method under test
        # Note: from_db is mocked, so this test primarily validates the parameter configuration
        result = WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=fake.word(),
            label=fake.word(),
            icon={"type": "emoji", "emoji": "📁"},
            description=fake.text(max_nb_chars=200),
            parameters=files_parameters,
        )

        # Verify the result
        assert result == {"result": "success"}

    def test_create_workflow_tool_db_commit_before_validation(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test that database commit happens before validation, causing DB pollution on validation failure.

        This test verifies the second bug:
        - WorkflowToolProvider is committed to database BEFORE from_db validation
        - If validation fails, the record remains in the database
        - Subsequent attempts fail with "Tool already exists" error

        This demonstrates why we need to validate BEFORE database commit.
        """

        fake = Faker()

        # Create test data
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        tool_name = fake.word()

        # Mock from_db to raise validation error
        mock_external_service_dependencies["workflow_tool_provider_controller"].from_db.side_effect = ValueError(
            "Validation failed: default parameter type mismatch"
        )

        # Attempt to create workflow tool (will fail at validation stage)
        with pytest.raises(ValueError) as exc_info:
            WorkflowToolManageService.create_workflow_tool(
                user_id=account.id,
                tenant_id=account.current_tenant.id,
                workflow_app_id=app.id,
                name=tool_name,
                label=fake.word(),
                icon={"type": "emoji", "emoji": "🔧"},
                description=fake.text(max_nb_chars=200),
                parameters=self._create_test_workflow_tool_parameters(),
            )

        assert "Validation failed" in str(exc_info.value)

        # Verify the tool was NOT created in database
        # This is the expected behavior (no pollution)

        tool_count = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(
                WorkflowToolProvider.tenant_id == account.current_tenant.id,
                WorkflowToolProvider.name == tool_name,
            )
            .count()
        )

        # The record should NOT exist because the transaction should be rolled back
        # Currently, due to the bug, the record might exist (this test documents the bug)
        # After the fix, this should always be 0
        # For now, we document that the record may exist, demonstrating the bug
        # assert tool_count == 0  # Expected after fix

    def test_delete_workflow_tool_success(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test successful deletion of a workflow tool."""
        fake = Faker()
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )
        tool_name = fake.unique.word()

        WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=tool_name,
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=self._create_test_workflow_tool_parameters(),
        )

        tool = (
            db_session_with_containers.query(WorkflowToolProvider)
            .where(WorkflowToolProvider.tenant_id == account.current_tenant.id, WorkflowToolProvider.name == tool_name)
            .first()
        )
        assert tool is not None

        result = WorkflowToolManageService.delete_workflow_tool(account.id, account.current_tenant.id, tool.id)

        assert result == {"result": "success"}
        deleted = (
            db_session_with_containers.query(WorkflowToolProvider).where(WorkflowToolProvider.id == tool.id).first()
        )
        assert deleted is None

    def test_list_tenant_workflow_tools_empty(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test listing workflow tools when none exist returns empty list."""
        fake = Faker()
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        result = WorkflowToolManageService.list_tenant_workflow_tools(account.id, account.current_tenant.id)

        assert result == []

    def test_get_workflow_tool_by_tool_id_not_found(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that get_workflow_tool_by_tool_id raises ValueError when tool not found."""
        fake = Faker()
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        with pytest.raises(ValueError, match="Tool not found"):
            WorkflowToolManageService.get_workflow_tool_by_tool_id(account.id, account.current_tenant.id, fake.uuid4())

    def test_get_workflow_tool_by_app_id_not_found(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that get_workflow_tool_by_app_id raises ValueError when tool not found."""
        fake = Faker()
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        with pytest.raises(ValueError, match="Tool not found"):
            WorkflowToolManageService.get_workflow_tool_by_app_id(account.id, account.current_tenant.id, fake.uuid4())

    def test_list_single_workflow_tools_not_found(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that list_single_workflow_tools raises ValueError when tool not found."""
        fake = Faker()
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        with pytest.raises(ValueError, match="not found"):
            WorkflowToolManageService.list_single_workflow_tools(account.id, account.current_tenant.id, fake.uuid4())

    def test_create_workflow_tool_with_labels(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that labels are forwarded to ToolLabelManager when provided."""
        fake = Faker()
        app, account, workflow = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )

        result = WorkflowToolManageService.create_workflow_tool(
            user_id=account.id,
            tenant_id=account.current_tenant.id,
            workflow_app_id=app.id,
            name=fake.unique.word(),
            label=fake.word(),
            icon={"type": "emoji", "emoji": "🔧"},
            description=fake.text(max_nb_chars=200),
            parameters=self._create_test_workflow_tool_parameters(),
            labels=["label-1", "label-2"],
        )

        assert result == {"result": "success"}
        mock_external_service_dependencies["tool_label_manager"].update_tool_labels.assert_called_once()
