# Copyright 2024 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 base server-side task classes."""

from pathlib import Path
from typing import Optional

from django.test import TestCase

from debusine.artifacts.local_artifact import WorkRequestDebugLogs
from debusine.db.models import Artifact, ArtifactRelation, Workspace
from debusine.server.tasks.base import BaseInternalTask
from debusine.tasks.models import BaseTaskData, WorkerType
from debusine.tasks.tests.test_task import TestBaseTask2Data
from debusine.test import TestHelpersMixin


class TestBaseInternalTask(BaseInternalTask[BaseTaskData]):
    """Sample class to test BaseInternalTask class."""


class TestBaseInternalTask2(BaseInternalTask[TestBaseTask2Data]):
    """Test BaseInternalTask class with jsonschema validation."""

    TASK_VERSION = 1


class BaseInternalTaskTests(TestHelpersMixin, TestCase):
    """Unit tests for :class:`BaseInternalTask`."""

    def setUp(self) -> None:
        """Create the shared attributes."""
        self.task = TestBaseInternalTask({})
        self.task2 = TestBaseInternalTask2({"foo": "bar"})
        self.worker_metadata = {"system:worker_type": WorkerType.CELERY}

    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.worker_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.worker_metadata, **self.task.analyze_worker()}
        metadata["testbaseinternaltask:version"] = 1
        self.assertEqual(self.task.can_run_on(metadata), False)

    def setup_upload_work_request_debug_logs(
        self, source_artifacts: Optional[list[Artifact]] = None
    ):
        """Setup for upload_work_request_debug_logs tests."""  # noqa: D401
        # Add a file to be uploaded
        with self.task.open_debug_log_file("test.log") as file:
            file.write("log")
        assert self.task._debug_log_files_directory is not None

        self.task.workspace = "System"
        self.workspace = Workspace.objects.get(name=self.task.workspace)
        self.task.work_request = self.create_work_request().id
        self.task._source_artifacts_ids = [
            artifact.id for artifact in source_artifacts or []
        ]

        return [
            (path.name, path.read_bytes())
            for path in Path(
                self.task._debug_log_files_directory.name
            ).iterdir()
        ]

    def assert_uploaded_work_request_debug_logs_artifact(
        self,
        workspace: Workspace,
        work_request_id: int,
        expected_files: list[tuple[str, bytes]],
    ) -> Artifact:
        """Assert that an Artifact was created."""
        artifact = Artifact.objects.filter(
            category=WorkRequestDebugLogs._category
        ).last()
        assert artifact is not None
        assert artifact.created_by_work_request is not None

        self.assertEqual(artifact.category, WorkRequestDebugLogs._category)
        self.assertEqual(artifact.workspace, workspace)
        self.assertEqual(artifact.data, {})
        self.assertEqual(artifact.created_by_work_request.id, work_request_id)
        file_store = workspace.default_file_store.get_backend_object()
        for file_in_artifact, (name, contents) in zip(
            artifact.fileinartifact_set.order_by("id"), expected_files
        ):
            self.assertEqual(file_in_artifact.path, name)
            assert file_in_artifact.file is not None
            with file_store.get_stream(file_in_artifact.file) as file:
                self.assertEqual(file.read(), contents)

        return artifact

    def test_upload_work_request_debug_logs_with_relation(self):
        """
        Artifact is created and uploaded. Relation is created.

        The relation is from the debug logs artifact to the source_artifact_id.
        """
        source_artifact = self.create_artifact()[0]
        expected_files = self.setup_upload_work_request_debug_logs(
            [source_artifact]
        )

        self.task._upload_work_request_debug_logs()

        artifact = self.assert_uploaded_work_request_debug_logs_artifact(
            self.workspace, self.task.work_request, expected_files
        )

        self.assertEqual(artifact.relations.count(), 1)
        relation = artifact.relations.first()
        assert relation is not None
        self.assertEqual(relation.artifact, artifact)
        self.assertEqual(relation.target, source_artifact)
        self.assertEqual(relation.type, ArtifactRelation.Relations.RELATES_TO)

    def test_upload_work_request_debug_logs(self):
        """Artifact is created and uploaded."""
        expected_files = self.setup_upload_work_request_debug_logs()

        self.task._upload_work_request_debug_logs()

        self.assert_uploaded_work_request_debug_logs_artifact(
            self.workspace, self.task.work_request, expected_files
        )

    def test_upload_work_request_no_log_files(self):
        """No log files: no artifact created."""
        self.task._upload_work_request_debug_logs()

        self.assertFalse(
            Artifact.objects.filter(
                category=WorkRequestDebugLogs._category
            ).exists()
        )
