"""Test suite for pyloadergen module."""

from __future__ import annotations

import platform
import tempfile
from pathlib import Path
from unittest import mock

import pytest

from sfi.pyloadergen import pyloadergen


class TestSelectTemplate:
    """Test template selection logic."""

    def test_windows_gui_template_non_debug(self):
        """Test Windows GUI template selection in non-debug mode."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", True):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", False):
                result = pyloadergen.select_template("gui", False)
                assert result == pyloadergen._WINDOWS_GUI_TEMPLATE

    def test_windows_console_template_non_debug(self):
        """Test Windows console template selection in non-debug mode."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", True):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", False):
                result = pyloadergen.select_template("console", False)
                assert result == pyloadergen._WINDOWS_CONSOLE_TEMPLATE

    def test_windows_gui_template_debug(self):
        """Test that debug mode forces console template on Windows."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", True):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", False):
                result = pyloadergen.select_template("gui", True)
                assert result == pyloadergen._WINDOWS_CONSOLE_TEMPLATE

    def test_macos_gui_template_non_debug(self):
        """Test macOS GUI template selection in non-debug mode."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", False):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", True):
                result = pyloadergen.select_template("gui", False)
                assert result == pyloadergen._MACOS_GUI_TEMPLATE

    def test_macos_console_template_debug(self):
        """Test that debug mode forces console template on macOS."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", False):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", True):
                result = pyloadergen.select_template("gui", True)
                assert result == pyloadergen._MACOS_CONSOLE_TEMPLATE

    def test_unix_gui_template_non_debug(self):
        """Test Unix GUI template selection in non-debug mode."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", False):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", False):
                result = pyloadergen.select_template("gui", False)
                assert result == pyloadergen._UNIX_GUI_TEMPLATE

    def test_unix_console_template_debug(self):
        """Test that debug mode forces console template on Unix."""
        with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", False):  # noqa: SIM117
            with mock.patch("sfi.pyloadergen.pyloadergen.is_macos", False):
                result = pyloadergen.select_template("gui", True)
                assert result == pyloadergen._UNIX_CONSOLE_TEMPLATE


class TestGenerateCSource:
    """Test C source code generation."""

    def test_replace_entry_file_placeholder(self):
        """Test that ENTRY_FILE placeholder is replaced."""
        template = "script: ${ENTRY_FILE}"
        result = pyloadergen.generate_c_source(template, "my_script.py", False)
        assert "my_script.py" in result
        assert "${ENTRY_FILE}" not in result

    def test_replace_debug_mode_placeholder_true(self):
        """Test that DEBUG_MODE placeholder is replaced with 1."""
        template = "debug: ${DEBUG_MODE}"
        result = pyloadergen.generate_c_source(template, "test.py", True)
        assert "debug: 1" in result
        assert "${DEBUG_MODE}" not in result

    def test_replace_debug_mode_placeholder_false(self):
        """Test that DEBUG_MODE placeholder is replaced with 0."""
        template = "debug: ${DEBUG_MODE}"
        result = pyloadergen.generate_c_source(template, "test.py", False)
        assert "debug: 0" in result
        assert "${DEBUG_MODE}" not in result

    def test_replace_both_placeholders(self):
        """Test that both placeholders are replaced correctly."""
        template = "file: ${ENTRY_FILE}, debug: ${DEBUG_MODE}"
        result = pyloadergen.generate_c_source(template, "main.py", True)
        assert "file: main.py" in result
        assert "debug: 1" in result
        assert "${ENTRY_FILE}" not in result
        assert "${DEBUG_MODE}" not in result


class TestGetCompilerArgs:
    """Test compiler argument retrieval."""

    def test_gcc_compiler_args(self):
        """Test GCC compiler arguments."""
        args = pyloadergen.get_compiler_args("gcc")
        assert "-std=c99" in args
        assert "-Wall" in args
        assert "-pedantic" in args
        assert "-Werror" in args

    def test_clang_compiler_args(self):
        """Test Clang compiler arguments."""
        args = pyloadergen.get_compiler_args("clang")
        assert "-std=c99" in args
        assert "-Wall" in args
        assert "-pedantic" in args
        assert "-Werror" in args

    def test_cl_compiler_args(self):
        """Test MSVC (cl) compiler arguments."""
        args = pyloadergen.get_compiler_args("cl")
        assert "/std:c99" in args
        assert "/Wall" in args
        assert "/WX" in args
        assert "/Werror" in args

    @pytest.mark.skipif(platform.system() != "Windows", reason="Only supported on Windows")
    def test_cl_exe_compiler_args(self):
        """Test cl.exe compiler arguments (full path)."""
        args = pyloadergen.get_compiler_args("C:\\vc\\bin\\cl.exe")
        assert "/std:c99" in args
        assert "/Wall" in args
        assert "/WX" in args
        assert "/Werror" in args

    def test_unknown_compiler_args(self):
        """Test unknown compiler returns empty list."""
        args = pyloadergen.get_compiler_args("unknown_compiler")
        assert args == []


class TestFindCompiler:
    """Test compiler detection."""

    def test_find_compiler_gcc(self):
        """Test finding gcc compiler."""
        with mock.patch("shutil.which") as mock_which:
            mock_which.return_value = "/usr/bin/gcc"
            with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", False):
                result = pyloadergen.find_compiler()
                assert result == "gcc"

    def test_find_compiler_clang(self):
        """Test finding clang compiler."""
        # Mock shutil.which to return clang for clang but not for gcc
        with mock.patch("shutil.which") as mock_which:

            def which_side_effect(cmd):
                # Return gcc None, clang first
                if cmd == "gcc":
                    return None
                elif cmd == "clang":
                    return "/usr/bin/clang"
                return None

            mock_which.side_effect = which_side_effect

            with mock.patch("sfi.pyloadergen.pyloadergen.is_windows", False):
                result = pyloadergen.find_compiler()
                assert result == "clang"

    def test_no_compiler_found(self):
        """Test when no compiler is found."""
        with mock.patch("shutil.which") as mock_which:
            mock_which.return_value = None
            result = pyloadergen.find_compiler()
            assert result is None


class TestCompilerOptions:
    """Test compiler options dictionary."""

    def test_compiler_options_has_gcc(self):
        """Test GCC options are defined."""
        assert "gcc" in pyloadergen._COMPILER_OPTIONS
        assert "-std=c99" in pyloadergen._COMPILER_OPTIONS["gcc"]

    def test_compiler_options_has_clang(self):
        """Test Clang options are defined."""
        assert "clang" in pyloadergen._COMPILER_OPTIONS
        assert "-std=c99" in pyloadergen._COMPILER_OPTIONS["clang"]

    def test_compiler_options_has_cl(self):
        """Test MSVC options are defined."""
        assert "cl" in pyloadergen._COMPILER_OPTIONS
        assert "/std:c99" in pyloadergen._COMPILER_OPTIONS["cl"]


class TestTemplateContent:
    """Test template content validation."""

    def test_windows_gui_template_has_winmain(self):
        """Test Windows GUI template has WinMain entry point."""
        assert "int APIENTRY WinMain" in pyloadergen._WINDOWS_GUI_TEMPLATE

    def test_windows_console_template_has_main(self):
        """Test Windows console template has main entry point."""
        assert "int main(" in pyloadergen._WINDOWS_CONSOLE_TEMPLATE

    def test_unix_gui_template_has_main(self):
        """Test Unix GUI template has main entry point."""
        assert "int main(" in pyloadergen._UNIX_GUI_TEMPLATE

    def test_unix_console_template_has_main(self):
        """Test Unix console template has main entry point."""
        assert "int main(" in pyloadergen._UNIX_CONSOLE_TEMPLATE

    def test_macos_gui_template_has_main(self):
        """Test macOS GUI template has main entry point."""
        assert "int main(" in pyloadergen._MACOS_GUI_TEMPLATE

    def test_macos_console_template_has_main(self):
        """Test macOS console template has main entry point."""
        assert "int main(" in pyloadergen._MACOS_CONSOLE_TEMPLATE

    def test_windows_templates_have_placeholder(self):
        """Test Windows templates contain placeholders."""
        assert "${ENTRY_FILE}" in pyloadergen._WINDOWS_GUI_TEMPLATE
        assert "${DEBUG_MODE}" in pyloadergen._WINDOWS_GUI_TEMPLATE
        assert "${ENTRY_FILE}" in pyloadergen._WINDOWS_CONSOLE_TEMPLATE

    def test_unix_templates_have_placeholder(self):
        """Test Unix templates contain placeholder."""
        assert "${ENTRY_FILE}" in pyloadergen._UNIX_GUI_TEMPLATE
        assert "${ENTRY_FILE}" in pyloadergen._UNIX_CONSOLE_TEMPLATE

    def test_macos_templates_have_placeholder(self):
        """Test macOS templates contain placeholder."""
        assert "${ENTRY_FILE}" in pyloadergen._MACOS_GUI_TEMPLATE
        assert "${ENTRY_FILE}" in pyloadergen._MACOS_CONSOLE_TEMPLATE


class TestCompileCSource:
    """Test C source compilation."""

    def test_compile_creates_executable(self):
        """Test compilation creates executable file."""
        with tempfile.TemporaryDirectory() as temp_dir:
            c_source = Path(temp_dir) / "test.c"
            c_source.write_text("#include <stdio.h>\nint main() { return 0; }")
            output = Path(temp_dir) / "test.exe"

            with mock.patch("subprocess.run") as mock_run:
                mock_run.return_value = mock.Mock(returncode=0, stdout="", stderr="")
                result = pyloadergen.compile_c_source(str(c_source), output, "gcc")
                assert result is True

    def test_compile_failure(self):
        """Test compilation failure."""
        with tempfile.TemporaryDirectory() as temp_dir:
            c_source = Path(temp_dir) / "test.c"
            c_source.write_text("invalid C code")
            output = Path(temp_dir) / "test.exe"

            with mock.patch("subprocess.run") as mock_run:
                mock_run.return_value = mock.Mock(returncode=1, stdout="", stderr="error")
                result = pyloadergen.compile_c_source(str(c_source), output, "gcc")
                assert result is False

    def test_compiler_not_found(self):
        """Test when compiler is not found."""
        with tempfile.TemporaryDirectory() as temp_dir:
            c_source = Path(temp_dir) / "test.c"
            c_source.write_text("int main() { return 0; }")
            output = Path(temp_dir) / "test.exe"

            with mock.patch("subprocess.run", side_effect=FileNotFoundError):
                result = pyloadergen.compile_c_source(str(c_source), output, "gcc")
                assert result is False

    def test_no_compiler_specified_finds_one(self):
        """Test auto-detection of compiler when none specified."""
        with tempfile.TemporaryDirectory() as temp_dir:
            c_source = Path(temp_dir) / "test.c"
            c_source.write_text("int main() { return 0; }")
            output = Path(temp_dir) / "test.exe"

            with mock.patch("subprocess.run") as mock_run:
                mock_run.return_value = mock.Mock(returncode=0, stdout="", stderr="")
                with mock.patch("sfi.pyloadergen.pyloadergen.find_compiler", return_value="gcc"):
                    result = pyloadergen.compile_c_source(str(c_source), output, None)
                    assert result is True
