"""Tests for pyservice."""

from __future__ import annotations

import itertools
import logging
import sys
import time
from collections.abc import Mapping
from collections.abc import Sequence
from concurrent.futures import Executor
from datetime import UTC
from datetime import datetime
from pathlib import Path
from threading import Thread
from typing import TYPE_CHECKING

import pytest
from conftest import make_fixture_simple
from conftest import make_params_simple
from dateutil.relativedelta import relativedelta

from Demo import Demo
from Demo import DemoConfig
from pyWinService import ARGPARSE_EXIT
from pyWinService import ARGUMENT_ERROR
from pyWinService import FAILURE
from pyWinService import SUCCESS
from pyWinService import Argument
from pyWinService import Command
from pyWinService import InvalidCommandNameError
from pyWinService import ServiceCommand
from pyWinService import ServiceConfig

if TYPE_CHECKING:  # pragma: no cover
    from collections.abc import Callable
    from typing import Any

    from _pytest.logging import LogCaptureFixture
    from _pytest.monkeypatch import MonkeyPatch

    from pyWinService import CommandResult

logger = logging.getLogger(__name__)

service_dir: Path = Path(__file__).parent.parent / "run"


class TestCommand:
    """Tests for Command."""

    @pytest.fixture(
        **make_fixture_simple(
            "args",
            [],
            [Argument("one")],
            [Argument("one"), Argument("two")],
        )
    )
    def args(self, request) -> Sequence[Argument]:  # noqa: ANN001
        """Args parameter."""
        return request.param

    @pytest.fixture(
        **make_fixture_simple(
            "kwargs",
            {},
            {"one": "one"},
            {"one": "one", "two": "two"},
        )
    )
    def kwargs(self, request) -> Mapping[str, Any]:  # noqa: ANN001
        """Kwargs parameter."""
        return request.param

    def test_args(self, args: Sequence[Argument]) -> None:
        """Test for Command.args."""
        obj: Command = Command(*args)

        assert obj.args is not None
        assert isinstance(obj.args, Sequence)
        assert all(a == b for a, b in zip(obj.args, args, strict=True))

    def test_kwargs(self, kwargs: Mapping[str, Any]) -> None:
        """Test for Command.kwargs."""
        obj: Command = Command(**kwargs)

        assert obj.kwargs is not None
        assert isinstance(obj.kwargs, Mapping)
        assert obj.kwargs == kwargs


class TestArgument:
    """Tests for Argument."""

    @pytest.fixture(
        **make_fixture_simple(
            "args",
            [],
            ["one"],
            ["one", "two"],
        )
    )
    def args(self, request) -> Sequence[Any]:  # noqa: ANN001
        """Args parameter."""
        return request.param

    @pytest.fixture(
        **make_fixture_simple(
            "kwargs",
            {},
            {"one": "one"},
            {"one": "one", "two": "two"},
        )
    )
    def kwargs(self, request) -> Mapping[str, Any]:  # noqa: ANN001
        """Kwargs parameter."""
        return request.param

    def test_args(self, args: Sequence[Any]) -> None:
        """Test for Argument.args."""
        obj: Argument = Argument("test", *args)

        assert obj.args is not None
        assert isinstance(obj.args, Sequence)
        assert all(a == b for a, b in zip(obj.args, ("test", *args), strict=True))

    def test_kwargs(self, kwargs: Mapping[str, Any]) -> None:
        """Test for Argument.kwargs."""
        obj: Argument = Argument("test", **kwargs)

        assert obj.kwargs is not None
        assert isinstance(obj.kwargs, Mapping)
        assert obj.kwargs == kwargs


class TestServiceCommand:
    """Tests for ServiceCommand."""

    def test_valid(self) -> None:
        """Test for a valid ServiceCommand."""
        command: Command = Command(help="valid command")

        @ServiceCommand.create(*command.args, **command.kwargs)
        def command_valid() -> CommandResult:
            return 0

        assert callable(command_valid)
        assert hasattr(command_valid, "command")
        assert isinstance(command_valid.command, Command)
        assert command_valid.command == command

    def test_invalid(self) -> None:
        """Test for an invalid ServiceCommand."""
        with pytest.raises(InvalidCommandNameError):

            @ServiceCommand.create(help="invalid command")
            def invalid() -> CommandResult:
                return 0


class TestServiceConfig:
    """Tests for ServiceConfig."""

    def test_dummy(self) -> None:
        """Function is here to make PyCharm add a quick-run gutter icon to TestServiceConfig."""

    class TestPathFuncs:
        """Tests for ServiceConfig.path_funcs()."""

        value_map: Mapping[str, tuple[str | None, Path | None]] = {
            "empty": (None, None),
            "relative": ("relative\\path", Path("relative/path")),
            "absolute": ("C:\\absolute\\path", Path("C:/absolute/path")),
        }

        @pytest.fixture(params=value_map.values(), ids=list(value_map.keys()))
        def values(self, request) -> tuple[str | None, Path | None]:  # noqa: ANN001
            """Args parameter."""
            return request.param

        def test_on_save(self) -> None:
            """Test for ServiceConfig.path_funcs()["on_save"]."""
            functions: Mapping[str, Any] = ServiceConfig.path_funcs()

            assert "on_save" in functions

            on_save: Callable[[Any], Any] = functions["on_save"]

            assert on_save is not None
            assert callable(on_save)

        def test_on_save_values(self, values: tuple[str | None, Path | None]) -> None:
            """Test for ServiceConfig.path_funcs()["on_save"]."""
            save_obj: Path | None
            load_obj: str | None
            load_obj, save_obj = values

            functions: Mapping[str, Any] = ServiceConfig.path_funcs()
            on_save: Callable[[Any], Any] = functions["on_save"]

            result: str | None = on_save(save_obj)

            if save_obj is None:
                assert result is None
            else:
                assert result is not None
                assert isinstance(result, str)
                assert result == load_obj

        def test_on_load(self) -> None:
            """Test for ServiceConfig.path_funcs()["on_load"]."""
            functions: Mapping[str, Any] = ServiceConfig.path_funcs()

            assert "on_load" in functions

            on_load: Callable[[Any], Any] = functions["on_load"]

            assert on_load is not None
            assert callable(on_load)

        def test_on_load_values(self, values: tuple[str | None, Path | None]) -> None:
            """Test for ServiceConfig.path_funcs()["on_load"]."""
            save_obj: Path | None
            load_obj: str | None
            load_obj, save_obj = values

            functions: Mapping[str, Any] = ServiceConfig.path_funcs()
            on_load: Callable[[Any], Any] = functions["on_load"]

            result: Path | None = on_load(load_obj)

            if load_obj is None:
                assert result is None
            else:
                assert result is not None
                assert isinstance(result, Path)
                assert result == save_obj

    class TestDatetimeFuncs:
        """Tests for ServiceConfig.datetime_funcs()."""

        value_map: Mapping[str, tuple[str, datetime]] = {
            "simple": ("2000-01-01T00:00:00", datetime(year=2000, month=1, day=1)),  # noqa: DTZ001
            "complex": (
                "2025-02-07T08:46:20.169351",
                datetime(  # noqa: DTZ001
                    year=2025, month=2, day=7, hour=8, minute=46, second=20, microsecond=169351
                ),
            ),
            "timezone": (
                "2025-02-07T08:46:20.169351+00:00",
                datetime(
                    year=2025,
                    month=2,
                    day=7,
                    hour=8,
                    minute=46,
                    second=20,
                    microsecond=169351,
                    tzinfo=UTC,
                ),
            ),
        }

        @pytest.fixture(params=value_map.values(), ids=list(value_map.keys()))
        def values(self, request) -> tuple[Mapping[str, int], relativedelta]:  # noqa: ANN001
            """Args parameter."""
            return request.param

        def test_on_save(self) -> None:
            """Test for ServiceConfig.datetime_funcs()["on_save"]."""
            functions: Mapping[str, Any] = ServiceConfig.datetime_funcs()

            assert "on_save" in functions

            on_save: Callable[[Any], Any] = functions["on_save"]

            assert on_save is not None
            assert callable(on_save)

        def test_on_save_values(self, values: tuple[str, datetime]) -> None:
            """Test for ServiceConfig.datetime_funcs()["on_save"]."""
            save_obj: datetime
            load_obj: str
            load_obj, save_obj = values

            functions: Mapping[str, Any] = ServiceConfig.datetime_funcs()
            on_save: Callable[[Any], Any] = functions["on_save"]

            result: str = on_save(save_obj)

            assert result is not None
            assert isinstance(result, str)
            assert result == load_obj

        def test_on_load(self) -> None:
            """Test for ServiceConfig.datetime_funcs()["on_load"]."""
            functions: Mapping[str, Any] = ServiceConfig.datetime_funcs()

            assert "on_load" in functions

            on_load: Callable[[Any], Any] = functions["on_load"]

            assert on_load is not None
            assert callable(on_load)

        def test_on_load_values(self, values: tuple[str, datetime]) -> None:
            """Test for ServiceConfig.datetime_funcs()["on_load"]."""
            save_obj: datetime
            load_obj: str
            load_obj, save_obj = values

            functions: Mapping[str, Any] = ServiceConfig.datetime_funcs()
            on_load: Callable[[Any], Any] = functions["on_load"]

            result: datetime = on_load(load_obj)

            assert result is not None
            assert isinstance(result, datetime)
            assert result == save_obj

    class TestRelativedeltaFuncs:
        """Tests for ServiceConfig.relativedelta_funcs()."""

        value_map: Mapping[str, tuple[Mapping[str, int], relativedelta]] = {
            "empty": ({"seconds": 0}, relativedelta()),
            "years": ({"years": 1}, relativedelta(years=1)),
            "months": ({"months": 1}, relativedelta(months=1)),
            "days": ({"days": 1}, relativedelta(days=1)),
            "leapdays": ({"leapdays": 1}, relativedelta(leapdays=1)),
            "hours": ({"hours": 1}, relativedelta(hours=1)),
            "minutes": ({"minutes": 1}, relativedelta(minutes=1)),
            "seconds": ({"seconds": 1}, relativedelta(seconds=1)),
            "microseconds": ({"microseconds": 1}, relativedelta(microseconds=1)),
        }

        @pytest.fixture(params=value_map.values(), ids=list(value_map.keys()))
        def values(self, request) -> tuple[Mapping[str, int], relativedelta]:  # noqa: ANN001
            """Args parameter."""
            return request.param

        def test_on_save(self) -> None:
            """Test for ServiceConfig.relativedelta_funcs()["on_save"]."""
            functions: Mapping[str, Any] = ServiceConfig.relativedelta_funcs()

            assert "on_save" in functions

            on_save: Callable[[Any], Any] = functions["on_save"]

            assert on_save is not None
            assert callable(on_save)

        def test_on_save_values(self, values: tuple[Mapping[str, int], relativedelta]) -> None:
            """Test for ServiceConfig.relativedelta_funcs()["on_save"]."""
            save_obj: relativedelta
            load_obj: Mapping[str, int]
            load_obj, save_obj = values

            functions: Mapping[str, Any] = ServiceConfig.relativedelta_funcs()
            on_save: Callable[[Any], Any] = functions["on_save"]

            result: Mapping[str, int] = on_save(save_obj)

            assert result is not None
            assert isinstance(result, Mapping)
            assert result == load_obj

        def test_on_load(self) -> None:
            """Test for ServiceConfig.relativedelta_funcs()["on_load"]."""
            functions: Mapping[str, Any] = ServiceConfig.relativedelta_funcs()

            assert "on_load" in functions

            on_load: Callable[[Any], Any] = functions["on_load"]

            assert on_load is not None
            assert callable(on_load)

        def test_on_load_values(self, values: tuple[Mapping[str, int], relativedelta]) -> None:
            """Test for ServiceConfig.relativedelta_funcs()["on_load"]."""
            save_obj: relativedelta
            load_obj: Mapping[str, int]
            load_obj, save_obj = values

            functions: Mapping[str, Any] = ServiceConfig.relativedelta_funcs()
            on_load: Callable[[Any], Any] = functions["on_load"]

            result: relativedelta = on_load(load_obj)

            assert result is not None
            assert isinstance(result, relativedelta)
            assert result == save_obj

    class TestSaveToFile:
        """Tests for ServiceConfig.save_to_file()."""

        def test_not_exist(self, tmp_path: Path) -> None:
            """Test for ServiceConfig.load_from_file(Path)."""
            file_path: Path = tmp_path / "config.json"
            assert not file_path.exists()

            config: ServiceConfig = ServiceConfig()

            config.save_to_file(file_path)
            assert file_path.exists()

            loaded_config: ServiceConfig = ServiceConfig.load_from_file(file_path)
            assert loaded_config == config

        def test_exist(self, tmp_path: Path) -> None:
            """Test for ServiceConfig.load_from_file(Path)."""
            file_path: Path = tmp_path / "config.json"
            original_config: ServiceConfig = ServiceConfig()
            original_config.save_to_file(file_path)
            assert file_path.exists()

            config: ServiceConfig = ServiceConfig()

            config.save_to_file(file_path)
            assert file_path.exists()

            loaded_config: ServiceConfig = ServiceConfig.load_from_file(file_path)
            assert loaded_config == config

    class TestLoadFromFile:
        """Tests for ServiceConfig.load_from_file()."""

        def test_not_exist(self, tmp_path: Path) -> None:
            """Test for ServiceConfig.load_from_file(Path)."""
            file_path: Path = tmp_path / "config.json"
            assert not file_path.exists()

            config: ServiceConfig = ServiceConfig.load_from_file(file_path)

            assert config is not None
            assert isinstance(config, ServiceConfig)
            assert file_path.exists()

        def test_exist(self, tmp_path: Path) -> None:
            """Test for ServiceConfig.load_from_file(Path)."""
            file_path: Path = tmp_path / "config.json"
            original_config: ServiceConfig = ServiceConfig()
            original_config.save_to_file(file_path)
            assert file_path.exists()

            config: ServiceConfig = ServiceConfig.load_from_file(file_path)

            assert config is not None
            assert isinstance(config, ServiceConfig)
            assert config == original_config


class TestService:
    """Tests for Service."""

    @pytest.fixture
    def instance(self, monkeypatch: MonkeyPatch) -> Demo:
        """Instance Parameter."""
        monkeypatch.chdir(service_dir)

        instance: Demo = Demo(())

        return instance

    def test_init(self, instance: Demo) -> None:
        """Test for Service.__init__()."""
        assert not instance.running

        assert instance.args is not None
        assert isinstance(instance.args, Sequence)

        assert instance.config_file is not None
        assert isinstance(instance.config_file, Path)
        assert instance.config_file.exists()
        assert instance.config_file.name == f"{instance.svc_name}.json"

        assert instance.config is not None
        assert isinstance(instance.config, DemoConfig)

    def test_stop(self, instance: Demo) -> None:
        """Test for Service.stop()."""
        assert not instance.running

        instance._stop_flag.clear()  # noqa: SLF001
        assert instance.running

        instance.stop()
        assert not instance.running

    class TestOnStart:
        """Tests for Service.on_start()."""

        def test_success(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_start() when it succeeds."""
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("Service finished in")
            assert caplog.messages[6] == "Demo Service Run Success"
            assert caplog.messages[7] == "Service stopping . . ."
            assert caplog.messages[8] == "Demo Service Stop"
            assert caplog.messages[9] == "Service stopped"

        def test_fail(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_start() when it fails."""
            instance.should_start_fail = True
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Unhandled exception during 'on_start':"

        def test_abort(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_start() when it aborts."""
            instance.should_start = False
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service start was aborted."

    class TestOnRun:
        """Tests for Service.on_run()."""

        def test_success(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_run() when it succeeds."""
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("Service finished in")
            assert caplog.messages[6] == "Demo Service Run Success"
            assert caplog.messages[7] == "Service stopping . . ."
            assert caplog.messages[8] == "Demo Service Stop"
            assert caplog.messages[9] == "Service stopped"

        def test_fail(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_run() when it fails."""
            instance.should_run_fail = True
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("An unhandled Exception")
            assert caplog.messages[6] == "Demo Service Run Failed"
            assert caplog.messages[7] == "Service stopping . . ."
            assert caplog.messages[8] == "Demo Service Stop"
            assert caplog.messages[9] == "Service stopped"

        def test_repeat_success(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_run() when it repeats and succeeds."""
            object.__setattr__(instance.config, "frequency", relativedelta(minutes=1))
            start_thread: Thread = Thread(target=instance.start)
            start_thread.start()
            time.sleep(1)
            instance.stop()
            start_thread.join()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("Service finished in")
            assert caplog.messages[6] == "Demo Service Run Success"
            assert caplog.messages[7].startswith("Process will start at")
            assert caplog.messages[8] == "Service stopping . . ."
            assert caplog.messages[9] == "Demo Service Stop"
            assert caplog.messages[10] == "Service stopped"

        def test_repeat_fail(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_run() when it repeats and fails."""
            object.__setattr__(instance.config, "frequency", relativedelta(minutes=1))
            instance.should_run_fail = True
            start_thread: Thread = Thread(target=instance.start)
            start_thread.start()
            time.sleep(1)
            instance.stop()
            start_thread.join()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("An unhandled Exception")
            assert caplog.messages[6] == "Demo Service Run Failed"
            assert caplog.messages[7].startswith("Process will start at")
            assert caplog.messages[8] == "Service stopping . . ."
            assert caplog.messages[9] == "Demo Service Stop"
            assert caplog.messages[10] == "Service stopped"

    class TestOnStop:
        """Tests for Service.on_stop()."""

        def test_success(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_stop() when it succeeds."""
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("Service finished in")
            assert caplog.messages[6] == "Demo Service Run Success"
            assert caplog.messages[7] == "Service stopping . . ."
            assert caplog.messages[8] == "Demo Service Stop"
            assert caplog.messages[9] == "Service stopped"

        def test_fail(self, instance: Demo, caplog: LogCaptureFixture) -> None:
            """Test for Service.on_stop() when it fails."""
            instance.should_stop_fail = True
            instance.start()

            assert caplog.messages[0] == "Service starting . . ."
            assert caplog.messages[1] == "Demo Service Start"
            assert caplog.messages[2] == "Service started"
            assert caplog.messages[3] == "Service Running . . ."
            assert caplog.messages[4] == "Demo Service Run"
            assert caplog.messages[5].startswith("Service finished in")
            assert caplog.messages[6] == "Demo Service Run Success"
            assert caplog.messages[7] == "Service stopping . . ."
            assert caplog.messages[8] == "Demo Service Stop"
            assert caplog.messages[9] == "Unhandled exception during 'on_stop':"
            assert caplog.messages[10] == "Service stopped"

    class TestExecutor:
        """Tests for Service.executor()."""

        def test_not_running(self, instance: Demo) -> None:
            """Test for Service.executor()."""
            assert not instance.running
            executor: Executor | None
            with instance.executor() as executor:
                assert executor is None

        def test_running(self, instance: Demo) -> None:
            """Test for Service.executor()."""
            instance._stop_flag.clear()  # noqa: SLF001
            assert instance.running

            executor: Executor | None
            with instance.executor() as executor:
                assert executor is not None
                assert isinstance(executor, Executor)
                assert executor is instance._executor  # noqa: SLF001

        def test_cancel(self, instance: Demo) -> None:
            """Test for Service.executor()."""

            def func(value: int) -> int:
                time.sleep(1)
                return value

            instance._stop_flag.clear()  # noqa: SLF001
            assert instance.running

            count: int = 20
            executor: Executor | None
            with instance.executor(max_workers=2) as executor:
                assert executor is not None
                assert executor is instance._executor  # noqa: SLF001

                accumulator: int = 0
                result: int
                for result in executor.map(func, itertools.repeat(1, count)):
                    accumulator += result
                    instance._stop_executor()  # noqa: SLF001

            assert accumulator < count
            assert instance._executor is None  # noqa: SLF001

    class TestRun:
        """Tests for Service.run()."""

        def test_none(self, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
            """Test for Service.run() with an unknown command."""
            monkeypatch.chdir(tmp_path)

            result: CommandResult = Demo.run()
            assert result == FAILURE

        def test_exit(self, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
            """Test for Service.run() with an unknown command."""
            monkeypatch.chdir(tmp_path)

            result: CommandResult = Demo.run("--help")
            assert result == ARGPARSE_EXIT

        def test_unknown_command(self, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
            """Test for Service.run() with an unknown command."""
            monkeypatch.chdir(tmp_path)

            result: CommandResult = Demo.run("UNKNOWN_COMMAND")
            assert result == ARGUMENT_ERROR

        def test_start_local(self, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
            """Test for Service.run() with the 'start_local' command."""
            monkeypatch.chdir(tmp_path)

            result: CommandResult = Demo.run("start_local")
            assert result == SUCCESS

        def test_gen_batch(self, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
            """Test for Service.run() with the 'gen_batch' command."""
            monkeypatch.chdir(tmp_path)

            result: CommandResult = Demo.run("gen_batch")
            assert result == SUCCESS

            file_name: str
            for file_name in ["Install", "Remove", "Start", "Stop", "Restart"]:
                file: Path = tmp_path / f"{Demo.svc_name}_{file_name}.bat"
                assert file.exists()

        @pytest.mark.parametrize(**make_params_simple("command", "install", "remove", "update", "stop", "start"))
        def test_windows_service_command(self, monkeypatch: MonkeyPatch, tmp_path: Path, command: str) -> None:
            """Test for Service.run() with an unknown command."""
            monkeypatch.chdir(tmp_path)

            result: CommandResult = Demo.run(command)
            assert result == SUCCESS

    class TestHandleMain:
        """Tests for Service.handle_main()."""

        def test_gen_batch(self, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
            """Tests for Service.handle_main()."""
            monkeypatch.chdir(tmp_path)
            monkeypatch.setattr(sys, "argv", [sys.argv[0], "gen_batch"])

            with pytest.raises(SystemExit):
                Demo.handle_main()

            file_name: str
            for file_name in ["Install", "Remove", "Start", "Stop", "Restart"]:
                file: Path = tmp_path / f"{Demo.svc_name}_{file_name}.bat"
                assert file.exists()


if __name__ == "__main__":
    pytest.main()
