# 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.

"""Tests for the test playground functions."""

from typing import ClassVar

from debusine.db.models import Artifact, FileInArtifact, FileStore, Workspace
from debusine.test.django import TestCase, TransactionTestCase
from debusine.test.playground import Playground


class MemoryPlaygroundTestCaseTest(TestCase):
    """Test instantiation of playground in normal test case."""

    def test_instantiated(self) -> None:
        """Playground is instantiated."""
        self.assertIsInstance(self.playground, Playground)
        self.assertEqual(
            self.playground.file_store.backend, FileStore.BackendChoices.MEMORY
        )


class LocalPlaygroundTestCaseTest(TestCase):
    """Test instantiation of playground in normal test case."""

    playground_memory_file_store = False

    def test_instantiated(self) -> None:
        """Playground is instantiated."""
        self.assertIsInstance(self.playground, Playground)
        self.assertEqual(
            self.playground.file_store.backend, FileStore.BackendChoices.LOCAL
        )


class NoPlaygroundTestCaseTest(TestCase):
    """Test instantiation of playground in normal test case."""

    playground_needed = False

    def test_instantiated(self) -> None:
        """Playground is instantiated."""
        self.assertFalse(hasattr(self, "playground"))


class MemoryPlaygroundTransactionTestCaseTest(TransactionTestCase):
    """Test instantiation of playground in normal test case."""

    def test_instantiated(self) -> None:
        """Playground is instantiated."""
        self.assertIsInstance(self.playground, Playground)
        self.assertEqual(
            self.playground.file_store.backend, FileStore.BackendChoices.MEMORY
        )


class LocalPlaygroundTransactionTestCaseTest(TransactionTestCase):
    """Test instantiation of playground in normal test case."""

    playground_memory_file_store = False

    def test_instantiated(self) -> None:
        """Playground is instantiated."""
        self.assertIsInstance(self.playground, Playground)
        self.assertEqual(
            self.playground.file_store.backend, FileStore.BackendChoices.LOCAL
        )


class NoPlaygroundTransactionTestCaseTest(TransactionTestCase):
    """Test instantiation of playground in normal test case."""

    playground_needed = False

    def test_instantiated(self) -> None:
        """Playground is instantiated."""
        self.assertFalse(hasattr(self, "playground"))


class PlaygroundTransactionalTestMixin(TestCase):
    """Test restoring state across test methods."""

    # This is a common set of tests for both the in-memory store case and the
    # local file store case. This class is deleted at the end of the module to
    # avoid it being run as unittests

    # See https://docs.python.org/3/library/unittest.html#organizing-test-code
    # "The order in which the various tests will be run is determined by
    # sorting the test method names with respect to the built-in ordering for
    # strings."

    workspace: ClassVar[Workspace]
    art1: ClassVar[Artifact]
    art2: ClassVar[Artifact]

    @classmethod
    def setUpTestData(cls) -> None:
        """Create a playground to setup test data."""
        super().setUpTestData()
        cls.workspace = cls.playground.get_default_workspace()
        cls.art1, _ = cls.playground.create_artifact(
            {"test.deb": b"testdeb"}, create_files=True
        )
        cls.art2, _ = cls.playground.create_artifact(
            {"test.dsc": b"testdsc", "test.tar.xz": b"testtarxz"},
            create_files=True,
        )

    def assertTestFixtureUnchanged(self) -> None:
        """Check that the text fixture is as originally set up."""
        self.assertEqual(self.playground.default_username, "playground")
        files1 = list(self.art1.files.order_by("id"))
        files2 = list(self.art2.files.order_by("id"))
        self.assertEqual(len(files1), 1)
        self.assertEqual(len(files2), 2)
        self.assertEqual(Artifact.objects.count(), 2)
        backend = self.workspace.default_file_store.get_backend_object()
        with backend.get_stream(files1[0]) as fd:
            self.assertEqual(fd.read(), b"testdeb")
        with backend.get_stream(files2[0]) as fd:
            self.assertEqual(fd.read(), b"testdsc")
        with backend.get_stream(files2[1]) as fd:
            self.assertEqual(fd.read(), b"testtarxz")

    def test_alter_file_storage_1(self) -> None:
        """First part of testing transactional file storage behaviour."""
        self.assertTestFixtureUnchanged()
        self.playground.default_username = "changed_username"
        self.assertEqual(self.playground.default_username, "changed_username")

        # Add a new artifact
        self.playground.create_artifact(
            paths={"testfile": b"testdata"}, create_files=True
        )

        # Add a file to an existing artifact
        fileobj = self.playground.create_file_in_backend(
            contents=b"newfilecontents"
        )
        FileInArtifact.objects.create(
            artifact=self.art1, path="newfile", file=fileobj, complete=True
        )

        # Remove a file
        backend = self.playground.get_default_file_store().get_backend_object()
        backend.remove_file(self.art1.files.earliest("id"))

    def test_alter_file_storage_2(self) -> None:
        """Second part of testing transactional file storage behaviour."""
        self.assertTestFixtureUnchanged()


class PlaygroundTransactionalTestMemoryStore(
    PlaygroundTransactionalTestMixin,
):
    """Test transactional behaviour with in-memory file store."""


class PlaygroundTransactionalTestFileStore(PlaygroundTransactionalTestMixin):
    """Test transactional behaviour with on-disk file store."""

    playground_memory_file_store = False


# Avoid running tests from the common base
del PlaygroundTransactionalTestMixin
