from unittest.mock import MagicMock

import pytest

from core.app.apps.base_app_generator import BaseAppGenerator
from dify_graph.variables.input_entities import VariableEntity, VariableEntityType


def test_validate_inputs_with_zero():
    base_app_generator = BaseAppGenerator()

    var = VariableEntity(
        variable="test_var",
        label="test_var",
        type=VariableEntityType.NUMBER,
        required=True,
    )

    # Test with input 0
    result = base_app_generator._validate_inputs(
        variable_entity=var,
        value=0,
    )

    assert result == 0

    # Test with input "0" (string)
    result = base_app_generator._validate_inputs(
        variable_entity=var,
        value="0",
    )

    assert result == 0


def test_validate_input_with_none_for_required_variable():
    base_app_generator = BaseAppGenerator()

    for var_type in VariableEntityType:
        var = VariableEntity(
            variable="test_var",
            label="test_var",
            type=var_type,
            required=True,
        )

        # Test with input None
        with pytest.raises(ValueError) as exc_info:
            base_app_generator._validate_inputs(
                variable_entity=var,
                value=None,
            )

        assert str(exc_info.value) == "test_var is required in input form"


def test_validate_inputs_with_default_value():
    """Test that default values are used when input is None for optional variables"""
    base_app_generator = BaseAppGenerator()

    # Test with string default value for TEXT_INPUT
    var_string = VariableEntity(
        variable="test_var",
        label="test_var",
        type=VariableEntityType.TEXT_INPUT,
        required=False,
        default="default_string",
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_string,
        value=None,
    )

    assert result == "default_string"

    # Test with string default value for PARAGRAPH
    var_paragraph = VariableEntity(
        variable="test_paragraph",
        label="test_paragraph",
        type=VariableEntityType.PARAGRAPH,
        required=False,
        default="default paragraph text",
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_paragraph,
        value=None,
    )

    assert result == "default paragraph text"

    # Test with SELECT default value
    var_select = VariableEntity(
        variable="test_select",
        label="test_select",
        type=VariableEntityType.SELECT,
        required=False,
        default="option1",
        options=["option1", "option2", "option3"],
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_select,
        value=None,
    )

    assert result == "option1"

    # Test with number default value (int)
    var_number_int = VariableEntity(
        variable="test_number_int",
        label="test_number_int",
        type=VariableEntityType.NUMBER,
        required=False,
        default=42,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_number_int,
        value=None,
    )

    assert result == 42

    # Test with number default value (float)
    var_number_float = VariableEntity(
        variable="test_number_float",
        label="test_number_float",
        type=VariableEntityType.NUMBER,
        required=False,
        default=3.14,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_number_float,
        value=None,
    )

    assert result == 3.14

    # Test with number default value as string (frontend sends as string)
    var_number_string = VariableEntity(
        variable="test_number_string",
        label="test_number_string",
        type=VariableEntityType.NUMBER,
        required=False,
        default="123",
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_number_string,
        value=None,
    )

    assert result == 123
    assert isinstance(result, int)

    # Test with float number default value as string
    var_number_float_string = VariableEntity(
        variable="test_number_float_string",
        label="test_number_float_string",
        type=VariableEntityType.NUMBER,
        required=False,
        default="45.67",
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_number_float_string,
        value=None,
    )

    assert result == 45.67
    assert isinstance(result, float)

    # Test with CHECKBOX default value (bool)
    var_checkbox_true = VariableEntity(
        variable="test_checkbox_true",
        label="test_checkbox_true",
        type=VariableEntityType.CHECKBOX,
        required=False,
        default=True,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_checkbox_true,
        value=None,
    )

    assert result is True

    var_checkbox_false = VariableEntity(
        variable="test_checkbox_false",
        label="test_checkbox_false",
        type=VariableEntityType.CHECKBOX,
        required=False,
        default=False,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_checkbox_false,
        value=None,
    )

    assert result is False

    # Test with None as explicit default value
    var_none_default = VariableEntity(
        variable="test_none",
        label="test_none",
        type=VariableEntityType.TEXT_INPUT,
        required=False,
        default=None,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_none_default,
        value=None,
    )

    assert result is None

    # Test that actual input value takes precedence over default
    result = base_app_generator._validate_inputs(
        variable_entity=var_string,
        value="actual_value",
    )

    assert result == "actual_value"

    # Test that actual number input takes precedence over default
    result = base_app_generator._validate_inputs(
        variable_entity=var_number_int,
        value=999,
    )

    assert result == 999

    # Test with FILE default value (dict format from frontend)
    var_file = VariableEntity(
        variable="test_file",
        label="test_file",
        type=VariableEntityType.FILE,
        required=False,
        default={"id": "file123", "name": "default.pdf"},
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_file,
        value=None,
    )

    assert result == {"id": "file123", "name": "default.pdf"}

    # Test with FILE_LIST default value (list of dicts)
    var_file_list = VariableEntity(
        variable="test_file_list",
        label="test_file_list",
        type=VariableEntityType.FILE_LIST,
        required=False,
        default=[{"id": "file1", "name": "doc1.pdf"}, {"id": "file2", "name": "doc2.pdf"}],
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_file_list,
        value=None,
    )

    assert result == [{"id": "file1", "name": "doc1.pdf"}, {"id": "file2", "name": "doc2.pdf"}]


def test_validate_inputs_optional_file_with_empty_string():
    """Test that optional FILE variable with empty string returns None"""
    base_app_generator = BaseAppGenerator()

    var_file = VariableEntity(
        variable="test_file",
        label="test_file",
        type=VariableEntityType.FILE,
        required=False,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_file,
        value="",
    )

    assert result is None


def test_validate_inputs_optional_file_list_with_empty_list():
    """Test that optional FILE_LIST variable with empty list returns empty list (not None)"""
    base_app_generator = BaseAppGenerator()

    var_file_list = VariableEntity(
        variable="test_file_list",
        label="test_file_list",
        type=VariableEntityType.FILE_LIST,
        required=False,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_file_list,
        value=[],
    )

    # Empty list should be preserved, not converted to None
    # This allows downstream components like document_extractor to handle empty lists properly
    assert result == []


def test_validate_inputs_optional_file_list_with_empty_string():
    """Test that optional FILE_LIST variable with empty string returns None"""
    base_app_generator = BaseAppGenerator()

    var_file_list = VariableEntity(
        variable="test_file_list",
        label="test_file_list",
        type=VariableEntityType.FILE_LIST,
        required=False,
    )

    result = base_app_generator._validate_inputs(
        variable_entity=var_file_list,
        value="",
    )

    # Empty string should be treated as unset
    assert result is None


def test_validate_inputs_required_file_with_empty_string_fails():
    """Test that required FILE variable with empty string still fails validation"""
    base_app_generator = BaseAppGenerator()

    var_file = VariableEntity(
        variable="test_file",
        label="test_file",
        type=VariableEntityType.FILE,
        required=True,
    )

    with pytest.raises(ValueError) as exc_info:
        base_app_generator._validate_inputs(
            variable_entity=var_file,
            value="",
        )

    assert "must be a file" in str(exc_info.value)


def test_validate_inputs_optional_file_with_empty_string_ignores_default():
    """Test that optional FILE variable with empty string returns None, not the default"""
    base_app_generator = BaseAppGenerator()

    var_file = VariableEntity(
        variable="test_file",
        label="test_file",
        type=VariableEntityType.FILE,
        required=False,
        default={"id": "file123", "name": "default.pdf"},
    )

    # When value is empty string (from frontend), should return None, not default
    result = base_app_generator._validate_inputs(
        variable_entity=var_file,
        value="",
    )

    assert result is None


class TestBaseAppGeneratorExtras:
    def test_prepare_user_inputs_converts_files_and_lists(self, monkeypatch):
        base_app_generator = BaseAppGenerator()

        variables = [
            VariableEntity(
                variable="file",
                label="file",
                type=VariableEntityType.FILE,
                required=False,
                allowed_file_types=[],
                allowed_file_extensions=[],
                allowed_file_upload_methods=[],
            ),
            VariableEntity(
                variable="file_list",
                label="file_list",
                type=VariableEntityType.FILE_LIST,
                required=False,
                allowed_file_types=[],
                allowed_file_extensions=[],
                allowed_file_upload_methods=[],
            ),
            VariableEntity(
                variable="json",
                label="json",
                type=VariableEntityType.JSON_OBJECT,
                required=False,
            ),
        ]

        monkeypatch.setattr(
            "core.app.apps.base_app_generator.file_factory.build_from_mapping",
            lambda mapping, tenant_id, config, strict_type_validation=False: "file-object",
        )
        monkeypatch.setattr(
            "core.app.apps.base_app_generator.file_factory.build_from_mappings",
            lambda mappings, tenant_id, config: ["file-1", "file-2"],
        )

        user_inputs = {
            "file": {"id": "file-id"},
            "file_list": [{"id": "file-1"}, {"id": "file-2"}],
            "json": {"key": "value"},
        }

        prepared = base_app_generator._prepare_user_inputs(
            user_inputs=user_inputs,
            variables=variables,
            tenant_id="tenant-id",
        )

        assert prepared["file"] == "file-object"
        assert prepared["file_list"] == ["file-1", "file-2"]
        assert prepared["json"] == {"key": "value"}

    def test_prepare_user_inputs_rejects_invalid_dict_inputs(self):
        base_app_generator = BaseAppGenerator()
        variables = [
            VariableEntity(
                variable="text",
                label="text",
                type=VariableEntityType.TEXT_INPUT,
                required=False,
            )
        ]

        with pytest.raises(ValueError, match="must be a string"):
            base_app_generator._prepare_user_inputs(
                user_inputs={"text": {"unexpected": "dict"}},
                variables=variables,
                tenant_id="tenant-id",
            )

    def test_prepare_user_inputs_rejects_invalid_list_inputs(self):
        base_app_generator = BaseAppGenerator()
        variables = [
            VariableEntity(
                variable="text",
                label="text",
                type=VariableEntityType.TEXT_INPUT,
                required=False,
            )
        ]

        with pytest.raises(ValueError, match="must be a string"):
            base_app_generator._prepare_user_inputs(
                user_inputs={"text": [{"unexpected": "dict"}]},
                variables=variables,
                tenant_id="tenant-id",
            )

    def test_convert_to_event_stream(self):
        base_app_generator = BaseAppGenerator()

        assert base_app_generator.convert_to_event_stream({"ok": True}) == {"ok": True}

        def _gen():
            yield {"delta": "hi"}
            yield "ping"

        converted = list(base_app_generator.convert_to_event_stream(_gen()))

        assert converted[0].startswith("data: ")
        assert "\n\n" in converted[0]
        assert converted[1] == "event: ping\n\n"

    def test_get_draft_var_saver_factory_debugger(self):
        from core.app.entities.app_invoke_entities import InvokeFrom
        from dify_graph.enums import BuiltinNodeTypes
        from models import Account

        base_app_generator = BaseAppGenerator()
        account = Account(name="Tester", email="tester@example.com")
        account.id = "account-id"
        account.tenant_id = "tenant-id"

        factory = base_app_generator._get_draft_var_saver_factory(InvokeFrom.DEBUGGER, account)
        saver = factory(
            session=MagicMock(),
            app_id="app-id",
            node_id="node-id",
            node_type=BuiltinNodeTypes.START,
            node_execution_id="node-exec-id",
        )

        assert saver is not None
