# 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 database aware task input value models."""

from typing import ClassVar, override

from debusine.artifacts.models import CollectionCategory
from debusine.client.models import LookupResultType
from debusine.db.models import Artifact, Collection, CollectionItem
from debusine.db.models.lookups import (
    LookupResultArtifact,
    LookupResultBare,
    LookupResultCollection,
)
from debusine.db.tasks.models import (
    DBArtifactInfo,
    DBBaseTaskInputArtifact,
    DBInputCollectionSingle,
    DBInputLookupResult,
)
from debusine.tasks.models import LookupSingle
from debusine.test.django import TestCase


class DBBaseTaskInputArtifactTests(TestCase):
    """Tests for :py:class:`DBBaseTaskInputArtifact`."""

    artifact: ClassVar[Artifact]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.artifact = cls.playground.create_minimal_source_package_artifact()

    def test_from_artifact(self) -> None:
        value = DBBaseTaskInputArtifact.from_artifact(self.artifact)
        self.assertEqual(value.artifact_id, self.artifact.id)
        self.assertEqual(value.category, self.artifact.category)
        self.assertEqual(value.data, self.artifact.create_data())
        self.assertIsNotNone(value._artifact)
        with self.assertNumQueries(0):
            self.assertEqual(value.artifact, self.artifact)

    def test_cache_miss(self) -> None:
        value = DBBaseTaskInputArtifact.from_artifact(self.artifact)
        value._artifact = None
        with self.assertNumQueries(1):
            self.assertEqual(value.artifact, self.artifact)


class DBArtifactInfoTests(TestCase):
    """Tests for :py:class:`DBArtifactInfo`."""

    artifact: ClassVar[Artifact]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.artifact = cls.playground.create_minimal_source_package_artifact()

    def test_from_artifact(self) -> None:
        value = DBArtifactInfo.from_artifact(
            self.artifact, label="label", lookup="lookup"
        )
        self.assertEqual(value.label, "label")
        self.assertEqual(value.lookup, "lookup")
        self.assertEqual(value.artifact_id, self.artifact.id)
        self.assertEqual(value.category, self.artifact.category)
        self.assertEqual(value.data, self.artifact.create_data())
        self.assertIsNotNone(value._artifact)
        with self.assertNumQueries(0):
            self.assertEqual(value.artifact, self.artifact)

    def test_cache_miss(self) -> None:
        value = DBArtifactInfo.from_artifact(
            self.artifact, label="label", lookup="lookup"
        )
        value._artifact = None
        with self.assertNumQueries(1):
            self.assertEqual(value.artifact, self.artifact)


class DBInputCollectionSingleTests(TestCase):
    """Tests for :py:class:`DBInputCollectionSingle`."""

    collection: ClassVar[Collection]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.collection = cls.playground.create_collection(
            "test", CollectionCategory.TEST
        )

    def test_from_collection(self) -> None:
        value = DBInputCollectionSingle.from_collection(
            self.collection, label="label", lookup="lookup"
        )
        self.assertEqual(value.label, "label")
        self.assertEqual(value.lookup, "lookup")
        self.assertEqual(value.collection_id, self.collection.id)
        self.assertEqual(value.scope_name, self.collection.workspace.scope.name)
        self.assertEqual(value.workspace_name, self.collection.workspace.name)
        self.assertEqual(value.category, self.collection.category)
        self.assertEqual(value.name, self.collection.name)
        self.assertEqual(value.data, self.collection.data)
        self.assertIsNotNone(value._collection)
        with self.assertNumQueries(0):
            self.assertEqual(value.collection, self.collection)

    def test_cache_miss(self) -> None:
        value = DBInputCollectionSingle.from_collection(
            self.collection, label="label", lookup="lookup"
        )
        value._collection = None
        with self.assertNumQueries(1):
            self.assertEqual(value.collection, self.collection)


class DBInputLookupResultTests(TestCase):
    """Tests for :py:class:`DBInputLookupResult`."""

    collection: ClassVar[Collection]
    collection_lookup: ClassVar[LookupSingle]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.collection = cls.playground.create_collection(
            "test", CollectionCategory.TEST
        )
        cls.collection_lookup = f"{cls.collection.pk}@collections"

    def make_lookup_result_bare(self) -> LookupResultBare:
        collection_item = self.playground.create_bare_data_item(
            parent_collection=self.collection, name="test"
        )
        return LookupResultBare(
            lookup="lookup",
            parent_collection_lookup=self.collection_lookup,
            collection_item=collection_item,
        )

    def make_lookup_result_artifact(self) -> LookupResultArtifact:
        artifact, _ = self.playground.create_artifact()
        collection_item = CollectionItem.objects.create_from_artifact(
            artifact,
            parent_collection=self.collection,
            name="test",
            data={},
            created_by_user=self.playground.get_default_user(),
        )
        return LookupResultArtifact(
            lookup="lookup",
            parent_collection_lookup=self.collection_lookup,
            collection_item=collection_item,
            artifact=artifact,
        )

    def make_lookup_result_collection(self) -> LookupResultCollection:
        sub_collection = self.playground.create_collection(
            name="sub", category=CollectionCategory.TEST
        )
        collection_item = CollectionItem.objects.create_from_collection(
            sub_collection,
            parent_collection=self.collection,
            name="test",
            data={},
            created_by_user=self.playground.get_default_user(),
        )
        return LookupResultCollection(
            lookup="lookup",
            parent_collection_lookup=self.collection_lookup,
            collection_item=collection_item,
            collection=sub_collection,
        )

    def test_from_lookup_result_bare_data(self) -> None:
        result = self.make_lookup_result_bare()
        value = DBInputLookupResult.from_lookup_result(result, label="label")
        self.assertEqual(value.label, "label")
        self.assertEqual(value.result_type, LookupResultType.BARE)
        self.assertEqual(value.parent_collection_lookup, self.collection_lookup)
        self.assertEqual(value.collection_item_id, result.collection_item.id)
        self.assertIsNone(value.artifact_id)
        self.assertIsNone(value.collection_id)
        with self.assertNumQueries(0):
            self.assertEqual(value.result, result)

    def test_from_lookup_result_artifact(self) -> None:
        result = self.make_lookup_result_artifact()
        assert result.collection_item is not None
        value = DBInputLookupResult.from_lookup_result(result, label="label")
        self.assertEqual(value.label, "label")
        self.assertEqual(value.result_type, LookupResultType.ARTIFACT)
        self.assertEqual(value.parent_collection_lookup, self.collection_lookup)
        self.assertEqual(value.collection_item_id, result.collection_item.id)
        self.assertEqual(value.artifact_id, result.artifact.id)
        self.assertIsNone(value.collection_id)
        with self.assertNumQueries(0):
            self.assertEqual(value.result, result)

    def test_from_lookup_result_collection(self) -> None:
        result = self.make_lookup_result_collection()
        assert result.collection_item is not None
        value = DBInputLookupResult.from_lookup_result(result, label="label")
        self.assertEqual(value.label, "label")
        self.assertEqual(value.result_type, LookupResultType.COLLECTION)
        self.assertEqual(value.parent_collection_lookup, self.collection_lookup)
        self.assertEqual(value.collection_item_id, result.collection_item.id)
        self.assertIsNone(value.artifact_id)
        self.assertEqual(value.collection_id, result.collection.id)
        with self.assertNumQueries(0):
            self.assertEqual(value.result, result)

    def test_cache_miss_bare_data(self) -> None:
        result = self.make_lookup_result_bare()
        value = DBInputLookupResult.from_lookup_result(result, label="label")
        value._result = None
        with self.assertNumQueries(1):
            self.assertEqual(value.result, result)

    def test_cache_miss_artifact(self) -> None:
        result = self.make_lookup_result_artifact()
        value = DBInputLookupResult.from_lookup_result(result, label="label")
        value._result = None
        with self.assertNumQueries(2):
            self.assertEqual(value.result, result)

    def test_cache_miss_collection(self) -> None:
        result = self.make_lookup_result_collection()
        value = DBInputLookupResult.from_lookup_result(result, label="label")
        value._result = None
        with self.assertNumQueries(2):
            self.assertEqual(value.result, result)
