# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Tests for the __main__ (Worker's CLI launcher)."""

import contextlib
import io
import signal
from pathlib import Path
from typing import override
from unittest import mock
from unittest.mock import AsyncMock

from debusine.test import TestCase
from debusine.worker.__main__ import main


class MainTests(TestCase):
    """Tests for __main__ (Worker's CLI launcher)."""

    @override
    def setUp(self) -> None:
        super().setUp()

        self.default_sigint_handler = signal.getsignal(signal.SIGINT)
        self.default_sigterm_handler = signal.getsignal(signal.SIGTERM)

        self.patch_config_handler()

    @override
    def tearDown(self) -> None:
        """Restore default signals (__main__.main() changes them)."""
        signal.signal(signal.SIGINT, self.default_sigint_handler)
        signal.signal(signal.SIGTERM, self.default_sigterm_handler)

        super().tearDown()

    def test_worker_version(self) -> None:
        stdout = io.StringIO()

        with (
            contextlib.redirect_stdout(stdout),
            self.assertRaisesSystemExit(0),
            mock.patch(
                "debusine.worker.__main__.debusine_version",
                return_value="0.1.0",
            ),
        ):
            main(["--version"])

        self.assertEqual("debusine-worker 0.1.0\n", stdout.getvalue())

    def test_worker_version_uninstalled(self) -> None:
        stdout = io.StringIO()

        with (
            contextlib.redirect_stdout(stdout),
            self.assertRaisesSystemExit(0),
            mock.patch(
                "debusine.worker.__main__.debusine_version", return_value=None
            ),
        ):
            main(["--version"])

        self.assertEqual(
            "debusine-worker uninstalled-version\n", stdout.getvalue()
        )

    def patch_config_handler(self) -> None:
        """Mock ConfigHandler to avoid trying to access config.ini."""
        config_handler_patcher = mock.patch(
            'debusine.worker._worker.ConfigHandler', autospec=True
        )
        self.config_handler_mocked = config_handler_patcher.start()
        instance = self.config_handler_mocked.return_value
        instance.log_level = None
        instance.working_directory = Path('/tmp')
        self.addCleanup(config_handler_patcher.stop)

    def patch_worker_main(self) -> mock.MagicMock:
        """Patch and return mock for Worker.main()."""
        patch = mock.patch(
            "debusine.worker.__main__.Worker.main", autospec=True
        )
        main_mocked = patch.start()
        self.addCleanup(patch.stop)

        return main_mocked

    def test_main_entry_point(self) -> None:
        """Worker's main entry point calls Worker().main()."""
        mocked_main = self.patch_worker_main()

        main([])

        mocked_main.assert_awaited_once()
        self.config_handler_mocked.assert_called()

    def patch_worker(self) -> mock.MagicMock:
        """Patch Worker class and returns its mock."""
        patch = mock.patch("debusine.worker.__main__.Worker")
        worker_mocked = patch.start()
        self.addCleanup(patch.stop)

        worker_mocked().main = AsyncMock()

        return worker_mocked

    def test_worker_parameters(self) -> None:
        """Worker main entry point calls run_worker() with the right params."""
        worker_mocked = self.patch_worker()

        main(
            [
                '--log-level',
                'DEBUG',
                '--working-directory',
                '/tmp',
            ]
        )

        worker_mocked.assert_called_with(
            log_level='DEBUG', log_stderr=False, working_directory='/tmp'
        )

    def test_default_worker_parameters(self) -> None:
        """Worker main entry point calls run_worker() with the right params."""
        worker_mocked = self.patch_worker()

        main([])

        worker_mocked.assert_called_with(
            log_level=None, log_stderr=False, working_directory=None
        )
