#  Copyright 2021 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.
"""Unit tests for the base Task class."""

from unittest import TestCase, mock

from debusine.tasks import Task, TaskConfigError
from debusine.tasks.sbuild import Sbuild


class TestTask(Task):
    """Sample class to test Task class."""


class TestTask2(Task):
    """Test Task class with jsonschema validation."""

    TASK_VERSION = 1

    TASK_DATA_SCHEMA = {
        "type": "object",
        "properties": {
            "foo": {
                "type": "string",
            },
            "bar": {
                "type": "number",
            },
        },
        "required": ["foo"],
    }


class BaseTaskTests(TestCase):
    """Unit tests for Task class."""

    def setUp(self) -> None:
        """Create the shared attributes."""
        self.task = TestTask()
        self.task2 = TestTask2()

    def test_name_is_set(self):
        """task.name is built from class name."""
        self.assertEqual(self.task.name, "testtask")

    def test_logger_is_configured(self):
        """task.logger is available."""
        self.assertIsNotNone(self.task.logger)

    def test_prefix_with_task_name(self):
        """task.prefix_with_task_name does what it says."""
        self.assertEqual(
            self.task.prefix_with_task_name("foobar"),
            f"{self.task.name}:foobar",
        )

    def test_configure_sets_data_attribute(self):
        """task.data is set with configure."""
        task_data = {"foo": "bar"}
        self.task.configure(task_data)

        self.assertEqual(self.task.data, task_data)

    def test_configure_fails_with_bad_schema(self):
        """configure() raises TaskConfigError if schema is not respected."""
        task_data = {}
        with self.assertRaises(TaskConfigError):
            self.task2.configure(task_data)
        # Ensure the data attribute is only set after a successful validation
        self.assertIsNone(self.task2.data)

    def test_configure_works_with_good_schema(self):
        """configure() doesn't raise TaskConfigError."""
        task_data = {"foo": "bar", "bar": 3.14}

        self.task2.configure(task_data)

        self.assertEqual(self.task2.data["foo"], "bar")
        self.assertEqual(self.task2.data["bar"], 3.14)

    def test_analyze_worker_without_task_version(self):
        """analyze_worker() reports an unknown task version."""
        metadata = self.task.analyze_worker()
        self.assertIsNone(metadata["testtask:version"])

    def test_analyze_worker_with_task_version(self):
        """analyze_worker() reports a task version."""
        metadata = self.task2.analyze_worker()
        self.assertEqual(metadata["testtask2:version"], 1)

    def test_analyze_worker_all_tasks(self):
        """analyze_worker_all_tasks() reports results for each task."""
        patcher = mock.patch.object(
            Task, '_sub_tasks', new={'task': TestTask, 'task2': TestTask2}
        )
        patcher.start()
        self.addCleanup(patcher.stop)

        metadata = self.task.analyze_worker_all_tasks()

        self.assertEqual(
            metadata,
            self.task2.analyze_worker() | self.task.analyze_worker(),
        )

        # Assert that metadata contains data
        self.assertNotEqual(metadata, {})

    def test_execute(self):
        """Ensure execute raises NotImplementedError."""
        with self.assertRaises(NotImplementedError):
            self.task.execute()

    def test_can_run_on_no_version(self):
        """Ensure can_run_on returns True if no version is specified."""
        self.assertIsNone(self.task.TASK_VERSION)
        metadata = self.task.analyze_worker()
        self.assertEqual(self.task.can_run_on(metadata), True)

    def test_can_run_on_with_different_versions(self):
        """Ensure can_run_on returns False if versions differ."""
        self.assertIsNone(self.task.TASK_VERSION)
        metadata = self.task.analyze_worker()
        metadata["testtask:version"] = 1
        self.assertEqual(self.task.can_run_on(metadata), False)

    def test_class_for_name_sbuild(self):
        """class_from_name returns Sbuild (case-insensitive)."""
        self.assertEqual(Task.class_from_name('sBuIlD'), Sbuild)

    def test_class_for_name_no_class(self):
        """class_from_name raises a ValueError exception."""
        with self.assertRaisesRegex(
            ValueError, "'non-existing-class' is not a registered task_name"
        ):
            Task.class_from_name('non-existing-class')

    def test_class_for_name_no_duplicates(self):
        """
        Task.__init_subclass__ raises AssertionError for duplicated names.

        Task.__init_subclass__ uses the class name in lowercase: it can cause
        unexpected duplicated names. Check that are detected.
        """
        with self.assertRaises(AssertionError):

            class Somesubclassname(Task):
                pass

            class SomeSubclassName(Task):
                pass
