
import pytest
from unittest.mock import Mock, mock_open, call
import os
import shutil
import re
from project_generator.infrastructure import generation_utils as utils


def _capitalize_typing_base(type_str: str) -> str:
    type_str = type_str.strip()
    base_mapping = {
        "dict": "Dict",
        "list": "List",
        "tuple": "Tuple",
        "set": "Set",
        "frozenset": "FrozenSet",
        "type": "Type",
    }

    def replacer(match):
        lower_type = match.group(1).lower()
        return base_mapping.get(lower_type, match.group(1))

    pattern = r'\b(' + '|'.join(re.escape(k) for k in base_mapping.keys()) + r')\b'
    capitalized_str = re.sub(pattern, replacer, type_str, flags=re.IGNORECASE)

    if type_str.lower() in base_mapping:
         return base_mapping[type_str.lower()]

    capitalized_str = capitalized_str.replace("[dict]", "[Dict]")
    capitalized_str = capitalized_str.replace("[list]", "[List]")
    capitalized_str = capitalized_str.replace("[tuple]", "[Tuple]")
    capitalized_str = capitalized_str.replace("[set]", "[Set]")

    return capitalized_str


utils._capitalize_typing_base = _capitalize_typing_base



@pytest.mark.parametrize("snake, camel", [
    ("my_tool_name", "MyToolName"),
    ("single", "Single"),
    ("", ""),
    ("name_with_1_number", "NameWith1Number"),
])
def test_to_camel_case(snake, camel):
    assert utils.to_camel_case(snake) == camel

@pytest.mark.parametrize("input_str, expected", [
    ("", []),
    (" ", []),
    ("name:str", [{"name": "name", "type": "str", "default": None, "optional": False}]),
    ("id:int, name:str", [
        {"name": "id", "type": "int", "default": None, "optional": False},
        {"name": "name", "type": "str", "default": None, "optional": False}
    ]),
    ("count:int=5", [{"name": "count", "type": "int", "default": "5", "optional": True}]),
    ("flag:bool=True, value: float = 1.0 ", [
        {"name": "flag", "type": "bool", "default": "True", "optional": True},
        {"name": "value", "type": "float", "default": "1.0", "optional": True}
    ]),
    (" onlyname ", [{"name": "onlyname", "type": "Any", "default": None, "optional": False}]),
    ("complex:List[Dict[str, Any]]", [{"name": "complex", "type": "List[Dict[str, Any]]", "default": None, "optional": False}]),
])
def test_parse_params(input_str, expected):

    assert utils.parse_params(input_str) == expected



@pytest.mark.parametrize("type_in, type_out", [
    ("dict", "Dict"),
    (" list ", "List"),
    ("Tuple", "Tuple"),
    ("set", "Set"),
    ("unknown", "unknown"),
    ("Dict[str, Any]", "Dict[str, Any]"),
    ("List[dict]", "List[Dict]"),
    (" list[ str ] ", "List[ str ]"),
    ("list[tuple[str, dict]]", "List[Tuple[str, Dict]]"),
])
def test_capitalize_typing_base(type_in, type_out):
    assert utils._capitalize_typing_base(type_in) == type_out




def test_create_file_success(mocker):
    mock_mko = mocker.patch("builtins.open", mock_open())
    mock_makedirs = mocker.patch("os.makedirs")
    utils.create_file("/fake/dir/file.txt", "content")
    mock_makedirs.assert_called_once_with("/fake/dir", exist_ok=True)
    mock_mko.assert_called_once_with("/fake/dir/file.txt", 'w')
    mock_mko().write.assert_called_once_with("content")

def test_create_file_os_error(mocker, capsys):
    mocker.patch("os.makedirs", side_effect=OSError("Permission denied"))
    utils.create_file("/fake/dir/file.txt", "content")
    captured = capsys.readouterr()
    assert "ERROR al crear archivo" in captured.out
    assert "Permission denied" in captured.out

def test_add_code_to_file_anchor_found(mocker):
    mock_mko = mocker.mock_open(read_data="line1\n# ANCHOR\nline3\n")
    mocker.patch("builtins.open", mock_mko)
    utils.add_code_to_file("file.txt", "# ANCHOR", "injected_code\n")
    mock_mko().writelines.assert_called_once_with(['line1\n', '# ANCHOR\n', 'injected_code\n', 'line3\n'])

def test_add_code_to_file_anchor_not_found(mocker, capsys):
    mock_mko = mocker.mock_open(read_data="line1\nline3")
    mocker.patch("builtins.open", mock_mko)
    utils.add_code_to_file("file.txt", "# NOT_FOUND", "injected_code\n")
    mock_mko().writelines.assert_called_once_with(['line1\n', 'line3', '\n', '\ninjected_code\n\n'])
    captured = capsys.readouterr()
    assert "ADVERTENCIA: Anchor '# NOT_FOUND' no encontrado" in captured.out

def test_add_code_to_file_not_found_error(mocker, capsys):
    mocker.patch("builtins.open", side_effect=FileNotFoundError("No such file"))
    utils.add_code_to_file("file.txt", "# ANCHOR", "code")
    captured = capsys.readouterr()
    assert "ADVERTENCIA: Archivo 'file.txt' no encontrado" in captured.out

def test_add_code_to_file_generic_error(mocker, capsys):
    mocker.patch("builtins.open", side_effect=Exception("Generic file error"))
    utils.add_code_to_file("file.txt", "# ANCHOR", "code")
    captured = capsys.readouterr()
    assert "ERROR inesperado al añadir código" in captured.out

def test_create_file_generic_error(mocker, capsys):
    mocker.patch("os.makedirs", side_effect=Exception("Generic makedirs error"))
    utils.create_file("/fake/dir/file.txt", "content")
    captured = capsys.readouterr()
    assert "ERROR inesperado al crear archivo" in captured.out

@pytest.fixture
def sample_params():
    return [
        {"name": "p_str", "type": "str", "default": None, "optional": False},
        {"name": "p_int", "type": "int", "default": "10", "optional": True},
        {"name": "p_bool", "type": "bool", "default": "True", "optional": True},
        {"name": "p_opt", "type": "Optional[str]", "default": None, "optional": True},
        {"name": "p_any", "type": "Any", "default": None, "optional": False},
    ]

def test_format_params_for_sig(sample_params):
    assert utils.format_params_for_sig(sample_params, include_self=True) == \
        "self, p_str: str, p_int: int = 10, p_bool: bool = True, p_opt: Optional[str], p_any: Any"
    assert utils.format_params_for_sig(sample_params, include_self=False) == \
        "p_str: str, p_int: int = 10, p_bool: bool = True, p_opt: Optional[str], p_any: Any"

def test_format_params_for_call(sample_params):
    assert utils.format_params_for_call(sample_params, include_self=True) == \
        "self, p_str, p_int, p_bool, p_opt, p_any"
    assert utils.format_params_for_call(sample_params, include_self=False) == \
        "p_str, p_int, p_bool, p_opt, p_any"

def test_format_params_for_test_values(sample_params):
    assert utils.format_params_for_test_values(sample_params) == \
        "'test_p_str', 123, True, None, 'test_p_any_value'"

    float_params = [
        {"name": "p_float", "type": "float", "default": None, "optional": False},
        {"name": "p_false", "type": "bool", "default": "False", "optional": True},
    ]
    assert utils.format_params_for_test_values(float_params) == "123.45, False"

def test_format_params_for_test_call_kwargs(sample_params):
    assert utils.format_params_for_test_call_kwargs(sample_params) == \
        "p_str='test_p_str', p_int=123, p_bool=True, p_opt=None, p_any='test_p_any_value'"

def test_format_params_for_test_call_kwargs_mismatch(sample_params, capsys):

    with pytest.MonkeyPatch.context() as m:
        m.setattr(utils, "format_params_for_test_values", lambda x: "")
        result = utils.format_params_for_test_call_kwargs(sample_params)

        assert result == "p_str='test_p_str', p_int='test_p_int', p_bool='test_p_bool', p_opt='test_p_opt', p_any='test_p_any'"
    captured = capsys.readouterr()
    assert "ADVERTENCIA: Discrepancia en número de parámetros" in captured.out


def test_all_get_templates_return_strings():

    assert "class MyToolNameResponse" in utils.get_domain_model_template("my_tool_name", "MyToolName")
    assert "class MyToolNameAdapter(ABC)" in utils.get_domain_gateway_template("my_tool_name", "MyToolName", "p:str", "MyToolNameResponse")
    assert "class MyToolNameError(Exception)" in utils.get_domain_error_template("my_tool_name", "MyToolNameError")
    assert "class MyToolNameUseCase" in utils.get_use_case_template("my_tool_name", "MyToolName", "p:str", "p", "MyToolNameResponse", "MyToolNameAdapter", "MyToolNameError")
    assert "class MyToolNameApiAdapter(MyToolNameAdapter)" in utils.get_adapter_template("my_tool_name", "MyToolName", "p:str", "p", "MyToolNameResponse", "MyToolNameAdapter", "MyToolNameError")
    assert "test_my_tool_name_success" in utils.get_adapter_test_template("my_tool_name", "MyToolName", "MyToolNameAdapter", "MyToolNameError", "MyToolNameResponse", [])
    assert "test_my_tool_name_use_case_success" in utils.get_use_case_test_template("my_tool_name", "MyToolName", "MyToolNameAdapter", "MyToolNameError", "MyToolNameResponse", [])
    assert "class GetMyPromptUseCase" in utils.get_prompt_use_case_template("my_prompt", "MyPrompt")
    assert "class GetMyResourceResourceGateway(ABC)" in utils.get_resource_gateway_template("my_resource", "MyResource", "p:str", "Dict")
    assert "class GetMyResourceUseCase" in utils.get_resource_use_case_template("my_resource", "MyResource", "p:str", "p", "Dict")
    assert "class GetMyResourceResourceAdapter(GetMyResourceResourceGateway)" in utils.get_resource_adapter_template("my_resource", "MyResource", "p:str", "p", "Dict")
    assert "my_tool_name_endpoint: str" in utils.get_settings_field_template("my_tool_name")
    assert '"my_tool_name_endpoint",' in utils.get_settings_validator_template("my_tool_name")
    assert "from src.domain.usecase.my_tool_name_use_case import MyToolNameUseCase" in utils.get_container_import_template("my_tool_name", "MyToolName")
    assert "from src.infrastructure.driven_adapters.api_connect_adapter.adapter.my_tool_name_api_adapter import MyToolNameApiAdapter" in utils.get_container_adapter_import_template("my_tool_name", "MyToolName")
    assert "my_tool_name_adapter = providers.Singleton(MyToolNameApiAdapter" in utils.get_container_adapter_template("my_tool_name", "MyToolName")
    assert "my_tool_name_use_case = providers.Singleton(MyToolNameUseCase" in utils.get_container_use_case_template("my_tool_name", "MyToolName")
    assert "from .adapter.my_tool_name_api_adapter import MyToolNameApiAdapter" in utils.get_adapter_init_import_template("my_tool_name", "MyToolName")
    assert '"MyToolNameApiAdapter",' in utils.get_adapter_init_all_template("my_tool_name", "MyToolName")
    assert "from src.domain.usecase.my_tool_name_use_case import MyToolNameUseCase" in utils.get_tools_import_template("my_tool_name", "MyToolName")
    assert "my_tool_name_use_case: MyToolNameUseCase" in utils.get_tools_provide_template("my_tool_name", "MyToolName")
    assert "Este es un placeholder para 'my_prompt'" in utils.get_prompt_content_template("my_prompt", "Desc")
    assert "@mcp.prompt(\"my_prompt\")" in utils.get_prompts_bind_template("my_prompt", "Desc")
    assert "async def my_prompt() -> str:" in utils.get_prompts_bind_template("my_prompt", "Desc")
    assert "@mcp.tool()" in utils.get_tools_bind_template("my_tool", "p:str", "p", "Desc")
    assert "async def my_tool(p:str) -> dict:" in utils.get_tools_bind_template("my_tool", "p:str", "p", "Desc")
    assert "from src.domain.usecase.get_my_prompt_usecase import GetMyPromptUseCase" in utils.get_container_prompt_import_template("my_prompt", "MyPrompt")
    assert "get_my_prompt_use_case = providers.Singleton(GetMyPromptUseCase" in utils.get_container_prompt_use_case_template("my_prompt", "MyPrompt")
    assert "from src.domain.usecase.get_my_prompt_usecase import GetMyPromptUseCase" in utils.get_prompts_import_template("my_prompt", "MyPrompt")
    assert "get_my_prompt_use_case: GetMyPromptUseCase" in utils.get_prompts_provide_template("my_prompt", "MyPrompt")
    assert "from src.domain.usecase.get_my_resource_usecase import GetMyResourceUseCase" in utils.get_container_resource_import_template("my_resource", "MyResource")
    assert "get_my_resource_adapter: providers.Provider[GetMyResourceResourceGateway]" in utils.get_container_resource_adapter_template("my_resource", "MyResource")
    assert "get_my_resource_use_case = providers.Singleton(GetMyResourceUseCase" in utils.get_container_resource_use_case_template("my_resource", "MyResource")
    assert "from src.domain.usecase.get_my_resource_usecase import GetMyResourceUseCase" in utils.get_resources_import_template("my_resource", "MyResource")
    assert "get_my_resource_use_case: GetMyResourceUseCase" in utils.get_resources_provide_template("my_resource", "MyResource")
    assert "@mcp.resource(\"uri://test\")" in utils.get_resources_bind_template("my_resource", "uri://test", "Desc", "p:str", "p", "Dict")
    assert "async def my_resource(p:str) -> Dict:" in utils.get_resources_bind_template("my_resource", "uri://test", "Desc", "p:str", "p", "Dict")

