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

"""Test for the main entry point of debusine."""

from datetime import datetime, timedelta, timezone
from unittest import TestCase

try:
    import pydantic.v1 as pydantic
except ImportError:
    import pydantic  # type: ignore

from debusine.client.models import (
    ArtifactCreateRequest,
    CollectionItemType,
    FileRequest,
    LookupMultipleResponse,
    LookupSingleResponse,
    RelationCreateRequest,
    StrictBaseModel,
    model_to_json_serializable_dict,
)
from debusine.test import TestHelpersMixin
from debusine.test.utils import (
    create_artifact_response,
    create_work_request_response,
)


class WorkRequestTests(TestCase):
    """Tests for WorkRequest."""

    def test_work_request__str__(self):
        """__str__() return WorkRequest: {_id}."""
        work_request_id = 5
        work_request = create_work_request_response(id=work_request_id)

        self.assertEqual(str(work_request), f'WorkRequest: {work_request_id}')


class FileTests(TestCase):
    """Tests for the FileRequest."""

    def setUp(self):
        """Set up testing objects."""
        self.size = 10
        self.checksums = {
            "sha256": "bf2cb58a68f684d95a3b78ef8f661c"
            "9a4e5b09e82cc8f9cc88cce90528caeb27"
        }
        self.file_type = "file"

        self.file = FileRequest(
            size=self.size, checksums=self.checksums, type=self.file_type
        )

    def test_fields(self):
        """Assert expected fields."""
        self.assertEqual(self.file.size, 10)
        self.assertEqual(self.file.checksums, self.checksums)
        self.assertEqual(self.file.type, self.file_type)

    def test_size_positive_integer(self):
        """Assert size cannot be negative."""
        with self.assertRaises(pydantic.ValidationError):
            FileRequest(
                size=-10, checksums=self.checksums, file_type=self.file_type
            )

    def test_size_zero_is_valid(self):
        """Assert size can be zero."""
        FileRequest(size=0, checksums=self.checksums, type=self.file_type)

    def test_file_type_only_file(self):
        """Assert file_type cannot be "link" (only "file")."""
        with self.assertRaises(pydantic.ValidationError):
            FileRequest(
                size=self.size, checksums=self.checksums, file_type="link"
            )

    def test_checksum_not_bigger_than_255(self):
        """Assert checksums cannot be longer than 255."""
        checksums = {"sha256": "a" * 256}
        with self.assertRaises(pydantic.ValidationError):
            FileRequest(
                size=self.size, checksums=checksums, file_type=self.file_type
            )


class ArtifactCreateRequestTests(TestHelpersMixin, TestCase):
    """Tests for the ArtifactCreateRequest."""

    def setUp(self):
        """Set up testing objects."""
        self.category = "debian"
        self.workspace = "debian-lts"
        self.files = {
            "README": FileRequest(
                size=10, checksums={"sha256": "aaaa"}, type="file"
            )
        }
        self.data = {}
        self.expire_at = datetime.now() + timedelta(days=1)

        self.artifact = ArtifactCreateRequest(
            category=self.category,
            workspace=self.workspace,
            files=self.files,
            data=self.data,
            expire_at=self.expire_at,
        )

    def test_fields(self):
        """Assert expected fields are accessible."""
        self.assertEqual(self.artifact.category, self.category)
        self.assertEqual(self.artifact.workspace, self.workspace)
        self.assertEqual(self.artifact.files, self.files)
        self.assertEqual(self.artifact.data, self.data)
        self.assertEqual(self.artifact.expire_at, self.expire_at)

    def test_valid_file(self):
        """Assert files must contain a file type."""
        files = {"README": "test"}
        with self.assertRaises(pydantic.ValidationError):
            self.artifact = ArtifactCreateRequest(
                category=self.category,
                workspace=self.workspace,
                files=files,
                data=self.data,
            )

    def test_create(self):
        """Assert create() returns the correct model."""
        contents = b"This is a test"

        file = self.create_temporary_file(contents=contents)

        file = FileRequest.create_from(file)
        self.assertEqual(file.size, len(contents))


class ArtifactResponseTests(TestCase):
    """Tests for ArtifactResponse."""

    def setUp(self):
        """Set up objects."""
        self.artifact_id = 5
        self.files_to_upload = ["README", "src/.dirstamp"]

        self.artifact = create_artifact_response(
            id=self.artifact_id,
            category="Testing",
            workspace="some workspace",
            created_at=datetime.utcnow(),
            download_tar_gz_url="https://example.com/some/path/",
            files_to_upload=self.files_to_upload,
        )

    def test_fields(self):
        """Test fields of ArtifactResponse."""
        self.assertEqual(self.artifact.id, self.artifact_id)
        self.assertEqual(self.artifact.files_to_upload, self.files_to_upload)


class RelationCreateRequestTests(TestCase):
    """Tests for RelationCreateRequest."""

    def setUp(self):
        """Set up test."""
        self.artifact_id = 1
        self.target_id = 2
        self.relation_type = "extends"

        self.relation = RelationCreateRequest(
            artifact=self.artifact_id,
            target=self.target_id,
            type=self.relation_type,
        )

    def test_create(self):
        """Test object has the expected fields."""
        self.assertEqual(self.relation.artifact, self.artifact_id)
        self.assertEqual(self.relation.target, self.target_id)
        self.assertEqual(self.relation.type, self.relation_type)

    def test_relation_types(self):
        """No exception is raised when setting valid relations."""
        valid_relation_types = ["extends", "relates-to", "built-using"]

        for relation_type in valid_relation_types:
            RelationCreateRequest(
                artifact=self.artifact_id,
                target=self.target_id,
                type=relation_type,
            )

    def test_relation_type_raise_error(self):
        """Exception is raised if a non-valid relation type is set."""
        with self.assertRaises(pydantic.ValidationError):
            RelationCreateRequest(artifact=1, target=2, type="does-not-exist")


class LookupMultipleResponseTests(TestCase):
    """Tests for LookupMultipleResponse."""

    def test_iter(self):
        """`__iter__` iterates over individual lookup results."""
        response = LookupMultipleResponse.parse_obj(
            [{"result_type": "a", "artifact": artifact} for artifact in (1, 2)]
        )

        self.assertEqual(
            list(response),
            [
                LookupSingleResponse(
                    result_type=CollectionItemType.ARTIFACT, artifact=artifact
                )
                for artifact in (1, 2)
            ],
        )


class StrictBaseClass(StrictBaseModel):
    """Class to instantiate in StrictBaseModelTests."""

    id: int


class StrictBaseModelTests(TestCase):
    """Tests for StrictBaseModel."""

    def test_invalid_field_content_assignment(self):
        """Raise ValidationError: assign a string to an int field."""
        id_ = 5
        base_class = StrictBaseClass(id=id_)
        self.assertEqual(base_class.id, id_)

        with self.assertRaises(pydantic.ValidationError):
            base_class.id = "invalid-value"

    def test_invalid_field_in_init(self):
        """Raise ValidationError: try to initialize a non-existing field."""
        with self.assertRaises(pydantic.ValidationError):
            StrictBaseClass(does_not_exist=20)


class ModelToTest(pydantic.BaseModel):
    """Simple model to be used in a test."""

    id: int
    created_at: datetime


class ModelToDictTests(TestCase):
    """Tests for model_to_json_serializable_dict function."""

    def test_model_to_json_serializable_dict(self):
        """model_to_json_serializable_dict return datetime as str."""
        obj_id = 4
        created_at = datetime.now(tz=timezone.utc)

        model = ModelToTest(id=obj_id, created_at=created_at)
        expected = {"id": obj_id, "created_at": created_at.isoformat()}

        self.assertEqual(model_to_json_serializable_dict(model), expected)
